diff --git a/app/app.go b/app/app.go index b339a59219..6a7274f719 100644 --- a/app/app.go +++ b/app/app.go @@ -390,13 +390,6 @@ func NewRegenApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper, groupConfig) // register custom modules here - ecocreditMod := ecocreditmodule.NewModule( - app.keys[ecocredit.ModuleName], - authtypes.NewModuleAddress(govtypes.ModuleName), - app.AccountKeeper, - app.BankKeeper, - app.GetSubspace(ecocredit.DefaultParamspace), - ) dataMod := datamodule.NewModule(app.keys[data.ModuleName], app.AccountKeeper, app.BankKeeper) govConfig := govtypes.DefaultConfig() @@ -404,6 +397,14 @@ func NewRegenApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, &stakingKeeper, govRouter, app.MsgServiceRouter(), govConfig, ) + ecocreditMod := ecocreditmodule.NewModule( + app.keys[ecocredit.ModuleName], + authtypes.NewModuleAddress(govtypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + app.GetSubspace(ecocredit.DefaultParamspace), + app.GovKeeper, + ) skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) diff --git a/x/ecocredit/base/simulation/operations.go b/x/ecocredit/base/simulation/operations.go index cc8ceec447..e6ed8b9aae 100644 --- a/x/ecocredit/base/simulation/operations.go +++ b/x/ecocredit/base/simulation/operations.go @@ -9,17 +9,20 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/orm/types/ormerrors" "github.com/cosmos/cosmos-sdk/simapp/helpers" simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/regen-network/regen-ledger/types/math" "github.com/regen-network/regen-ledger/x/ecocredit" "github.com/regen-network/regen-ledger/x/ecocredit/base" types "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" + basketsims "github.com/regen-network/regen-ledger/x/ecocredit/basket/simulation" baskettypes "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" marketsims "github.com/regen-network/regen-ledger/x/ecocredit/marketplace/simulation" markettypes "github.com/regen-network/regen-ledger/x/ecocredit/marketplace/types/v1" @@ -28,20 +31,25 @@ import ( // Simulation operation weights constants const ( - OpWeightMsgCreateClass = "op_weight_msg_create_class" //nolint:gosec - OpWeightMsgCreateBatch = "op_weight_msg_create_batch" //nolint:gosec - OpWeightMsgSend = "op_weight_msg_send" //nolint:gosec - OpWeightMsgRetire = "op_weight_msg_retire" //nolint:gosec - OpWeightMsgCancel = "op_weight_msg_cancel" //nolint:gosec - OpWeightMsgUpdateClassAdmin = "op_weight_msg_update_class_admin" //nolint:gosec - OpWeightMsgUpdateClassMetadata = "op_weight_msg_update_class_metadata" //nolint:gosec - OpWeightMsgUpdateClassIssuers = "op_weight_msg_update_class_issuers" //nolint:gosec - OpWeightMsgCreateProject = "op_weight_msg_create_project" //nolint:gosec - OpWeightMsgUpdateProjectAdmin = "op_weight_msg_update_project_admin" //nolint:gosec - OpWeightMsgUpdateProjectMetadata = "op_weight_msg_update_project_metadata" //nolint:gosec - OpWeightMsgMintBatchCredits = "op_weight_msg_mint_batch_credits" //nolint:gosec - OpWeightMsgSealBatch = "op_weight_msg_seal_batch" //nolint:gosec - OpWeightMsgBridge = "op_weight_msg_bridge" //nolint:gosec + OpWeightMsgCreateClass = "op_weight_msg_create_class" //nolint:gosec + OpWeightMsgCreateBatch = "op_weight_msg_create_batch" //nolint:gosec + OpWeightMsgSend = "op_weight_msg_send" //nolint:gosec + OpWeightMsgRetire = "op_weight_msg_retire" //nolint:gosec + OpWeightMsgCancel = "op_weight_msg_cancel" //nolint:gosec + OpWeightMsgUpdateClassAdmin = "op_weight_msg_update_class_admin" //nolint:gosec + OpWeightMsgUpdateClassMetadata = "op_weight_msg_update_class_metadata" //nolint:gosec + OpWeightMsgUpdateClassIssuers = "op_weight_msg_update_class_issuers" //nolint:gosec + OpWeightMsgCreateProject = "op_weight_msg_create_project" //nolint:gosec + OpWeightMsgUpdateProjectAdmin = "op_weight_msg_update_project_admin" //nolint:gosec + OpWeightMsgUpdateProjectMetadata = "op_weight_msg_update_project_metadata" //nolint:gosec + OpWeightMsgMintBatchCredits = "op_weight_msg_mint_batch_credits" //nolint:gosec + OpWeightMsgSealBatch = "op_weight_msg_seal_batch" //nolint:gosec + OpWeightMsgBridge = "op_weight_msg_bridge" //nolint:gosec + OpWeightMsgAddCreditType = "op_weight_msg_add_credit_type" //nolint:gosec + OpWeightMsgAddClassCreator = "op_weight_msg_add_class_creator" //nolint:gosec + OpWeightMsgRemoveClassCreator = "op_weight_msg_remove_class_creator" //nolint:gosec + OpWeightMsgSetClassCreatorAllowlist = "op_weight_msg_set_class_creator_allowlist" //nolint:gosec + OpWeightMsgUpdateClassFees = "op_weight_msg_update_class_fees" //nolint:gosec ) // ecocredit operations weights @@ -62,44 +70,55 @@ const ( // ecocredit message types var ( - TypeMsgCreateClass = sdk.MsgTypeURL(&types.MsgCreateClass{}) - TypeMsgCreateProject = sdk.MsgTypeURL(&types.MsgCreateProject{}) - TypeMsgCreateBatch = sdk.MsgTypeURL(&types.MsgCreateBatch{}) - TypeMsgSend = sdk.MsgTypeURL(&types.MsgSend{}) - TypeMsgRetire = sdk.MsgTypeURL(&types.MsgRetire{}) - TypeMsgCancel = sdk.MsgTypeURL(&types.MsgCancel{}) - TypeMsgUpdateClassAdmin = sdk.MsgTypeURL(&types.MsgUpdateClassAdmin{}) - TypeMsgUpdateClassIssuers = sdk.MsgTypeURL(&types.MsgUpdateClassIssuers{}) - TypeMsgUpdateClassMetadata = sdk.MsgTypeURL(&types.MsgUpdateClassMetadata{}) - TypeMsgUpdateProjectMetadata = sdk.MsgTypeURL(&types.MsgUpdateProjectMetadata{}) - TypeMsgUpdateProjectAdmin = sdk.MsgTypeURL(&types.MsgUpdateProjectAdmin{}) - TypeMsgBridge = sdk.MsgTypeURL(&types.MsgBridge{}) - TypeMsgMintBatchCredits = sdk.MsgTypeURL(&types.MsgMintBatchCredits{}) - TypeMsgSealBatch = sdk.MsgTypeURL(&types.MsgSealBatch{}) + TypeMsgCreateClass = sdk.MsgTypeURL(&types.MsgCreateClass{}) + TypeMsgCreateProject = sdk.MsgTypeURL(&types.MsgCreateProject{}) + TypeMsgCreateBatch = sdk.MsgTypeURL(&types.MsgCreateBatch{}) + TypeMsgSend = sdk.MsgTypeURL(&types.MsgSend{}) + TypeMsgRetire = sdk.MsgTypeURL(&types.MsgRetire{}) + TypeMsgCancel = sdk.MsgTypeURL(&types.MsgCancel{}) + TypeMsgUpdateClassAdmin = sdk.MsgTypeURL(&types.MsgUpdateClassAdmin{}) + TypeMsgUpdateClassIssuers = sdk.MsgTypeURL(&types.MsgUpdateClassIssuers{}) + TypeMsgUpdateClassMetadata = sdk.MsgTypeURL(&types.MsgUpdateClassMetadata{}) + TypeMsgUpdateProjectMetadata = sdk.MsgTypeURL(&types.MsgUpdateProjectMetadata{}) + TypeMsgUpdateProjectAdmin = sdk.MsgTypeURL(&types.MsgUpdateProjectAdmin{}) + TypeMsgBridge = sdk.MsgTypeURL(&types.MsgBridge{}) + TypeMsgMintBatchCredits = sdk.MsgTypeURL(&types.MsgMintBatchCredits{}) + TypeMsgSealBatch = sdk.MsgTypeURL(&types.MsgSealBatch{}) + TypeMsgAddCreditType = sdk.MsgTypeURL(&types.MsgAddCreditType{}) + TypeMsgAddClassCreator = sdk.MsgTypeURL(&types.MsgAddClassCreator{}) + TypeMsgRemoveClassCreator = sdk.MsgTypeURL(&types.MsgRemoveClassCreator{}) + TypeMsgSetClassCreatorAllowlist = sdk.MsgTypeURL(&types.MsgSetClassCreatorAllowlist{}) + TypeMsgUpdateClassFees = sdk.MsgTypeURL(&types.MsgUpdateClassFees{}) ) // WeightedOperations returns all the operations from the module with their respective weights func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + govk ecocredit.GovKeeper, qryClient types.QueryServer, basketQryClient baskettypes.QueryServer, - mktQryClient markettypes.QueryServer) simulation.WeightedOperations { + mktQryClient markettypes.QueryServer, authority sdk.AccAddress) simulation.WeightedOperations { var ( - weightMsgCreateClass int - weightMsgCreateBatch int - weightMsgSend int - weightMsgRetire int - weightMsgCancel int - weightMsgUpdateClassAdmin int - weightMsgUpdateClassIssuers int - weightMsgUpdateClassMetadata int - weightMsgCreateProject int - weightMsgUpdateProjectMetadata int - weightMsgUpdateProjectAdmin int - weightMsgSealBatch int - weightMsgMintBatchCredits int - weightMsgBridge int + weightMsgCreateClass int + weightMsgCreateBatch int + weightMsgSend int + weightMsgRetire int + weightMsgCancel int + weightMsgUpdateClassAdmin int + weightMsgUpdateClassIssuers int + weightMsgUpdateClassMetadata int + weightMsgCreateProject int + weightMsgUpdateProjectMetadata int + weightMsgUpdateProjectAdmin int + weightMsgSealBatch int + weightMsgMintBatchCredits int + weightMsgBridge int + weightMsgAddCreditType int + weightMsgAddClassCreator int + weightMsgRemoveClassCreator int + weightMsgSetClassCreatorAllowlist int + weightMsgUpdateClassFees int ) appParams.GetOrGenerate(cdc, OpWeightMsgCreateClass, &weightMsgCreateClass, nil, @@ -186,6 +205,36 @@ func WeightedOperations( }, ) + appParams.GetOrGenerate(cdc, OpWeightMsgAddCreditType, &weightMsgAddCreditType, nil, + func(_ *rand.Rand) { + weightMsgAddCreditType = WeightBridge + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgAddClassCreator, &weightMsgAddClassCreator, nil, + func(_ *rand.Rand) { + weightMsgAddClassCreator = WeightBridge + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgRemoveClassCreator, &weightMsgRemoveClassCreator, nil, + func(_ *rand.Rand) { + weightMsgRemoveClassCreator = WeightBridge + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgSetClassCreatorAllowlist, &weightMsgSetClassCreatorAllowlist, nil, + func(_ *rand.Rand) { + weightMsgSetClassCreatorAllowlist = WeightBridge + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgUpdateClassFees, &weightMsgUpdateClassFees, nil, + func(_ *rand.Rand) { + weightMsgUpdateClassFees = WeightBridge + }, + ) + ops := simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgCreateClass, @@ -244,12 +293,34 @@ func WeightedOperations( weightMsgBridge, SimulateMsgBridge(ak, bk, qryClient), ), + simulation.NewWeightedOperation( + weightMsgAddCreditType, + SimulateMsgAddCreditType(ak, bk, govk, qryClient, authority), + ), + + simulation.NewWeightedOperation( + weightMsgAddClassCreator, + SimulateMsgAddClassCreator(ak, bk, govk, qryClient, authority), + ), + + simulation.NewWeightedOperation( + weightMsgRemoveClassCreator, + SimulateMsgRemoveClassCreator(ak, bk, govk, qryClient, authority), + ), + + simulation.NewWeightedOperation( + weightMsgSetClassCreatorAllowlist, + SimulateMsgSetClassCreatorAllowlist(ak, bk, govk, qryClient, authority), + ), + simulation.NewWeightedOperation( + weightMsgUpdateClassFees, + SimulateMsgUpdateClassFees(ak, bk, govk, qryClient, authority), + ), } - // TODO: #1363 - // basketOps := basketsims.WeightedOperations(appParams, cdc, ak, bk, qryClient, basketQryClient) - // ops = append(ops, basketOps...) - marketplaceOps := marketsims.WeightedOperations(appParams, cdc, ak, bk, qryClient, mktQryClient) + basketOps := basketsims.WeightedOperations(appParams, cdc, ak, bk, govk, qryClient, basketQryClient, authority) + ops = append(ops, basketOps...) + marketplaceOps := marketsims.WeightedOperations(appParams, cdc, ak, bk, qryClient, mktQryClient, govk, authority) return append(ops, marketplaceOps...) } @@ -1218,6 +1289,348 @@ func SimulateMsgBridge(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, qryC } } +// SimulateMsgAddCreditType generates a MsgAddCreditType with random values. +func SimulateMsgAddCreditType(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, govk ecocredit.GovKeeper, + qryClient types.QueryServer, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgAddCreditType) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddCreditType, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddCreditType, "unable to generate deposit"), nil, err + } + + abbrev := simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 1, 3)) + abbrev = strings.ToUpper(abbrev) + name := simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 1, 10)) + + _, err = qryClient.CreditType(sdkCtx, &types.QueryCreditTypeRequest{ + Abbreviation: abbrev, + }) + if err != nil { + if !ormerrors.NotFound.Is(err) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddCreditType, err.Error()), nil, err + } + } + + proposalMsg := types.MsgAddCreditType{ + Authority: authority.String(), + CreditType: &types.CreditType{ + Abbreviation: abbrev, + Name: name, + Unit: "kg", + Precision: 6, + }, + } + + any, err := codectypes.NewAnyWithValue(&proposalMsg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddCreditType, err.Error()), nil, err + } + + msg := &govtypes.MsgSubmitProposal{ + Messages: []*codectypes.Any{any}, + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +// SimulateMsgAddClassCreator generates a MsgAddClassCreator with random values. +func SimulateMsgAddClassCreator(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, govk ecocredit.GovKeeper, + qryClient types.QueryServer, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgAddClassCreator) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddClassCreator, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddClassCreator, "unable to generate deposit"), nil, err + } + + creatorsResult, err := qryClient.AllowedClassCreators(sdkCtx, &types.QueryAllowedClassCreatorsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddClassCreator, err.Error()), nil, err + } + + if stringInSlice(proposerAddr, creatorsResult.ClassCreators) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddClassCreator, "class creator already exists"), nil, nil + } + + proposalMsg := types.MsgAddClassCreator{ + Authority: authority.String(), + Creator: proposerAddr, + } + + any, err := codectypes.NewAnyWithValue(&proposalMsg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddClassCreator, err.Error()), nil, err + } + + msg := &govtypes.MsgSubmitProposal{ + Messages: []*codectypes.Any{any}, + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +// SimulateMsgRemoveClassCreator generates a MsgRemoveClassCreator with random values. +func SimulateMsgRemoveClassCreator(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, govk ecocredit.GovKeeper, + qryClient types.QueryServer, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgRemoveClassCreator) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveClassCreator, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveClassCreator, "unable to generate deposit"), nil, err + } + + creatorsResult, err := qryClient.AllowedClassCreators(sdkCtx, &types.QueryAllowedClassCreatorsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveClassCreator, err.Error()), nil, err + } + + if !stringInSlice(proposerAddr, creatorsResult.ClassCreators) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveClassCreator, "unknown class creator"), nil, nil + } + + proposalMsg := types.MsgRemoveClassCreator{ + Authority: authority.String(), + Creator: proposerAddr, + } + + any, err := codectypes.NewAnyWithValue(&proposalMsg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveClassCreator, err.Error()), nil, err + } + + msg := &govtypes.MsgSubmitProposal{ + Messages: []*codectypes.Any{any}, + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +// SimulateMsgSetClassCreatorAllowlist generates a MsgSetClassCreatorAllowlist with random values. +func SimulateMsgSetClassCreatorAllowlist(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, govk ecocredit.GovKeeper, + qryClient types.QueryServer, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgSetClassCreatorAllowlist) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgSetClassCreatorAllowlist, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgSetClassCreatorAllowlist, "unable to generate deposit"), nil, err + } + + proposalMsg := types.MsgSetClassCreatorAllowlist{ + Authority: authority.String(), + Enabled: r.Float32() < 0.3, // 30% chance of allowlist being enabled, + } + + any, err := codectypes.NewAnyWithValue(&proposalMsg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgSetClassCreatorAllowlist, err.Error()), nil, err + } + + msg := &govtypes.MsgSubmitProposal{ + Messages: []*codectypes.Any{any}, + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +// SimulateMsgUpdateClassFees generates a MsgToggleClassAllowlist with random values. +func SimulateMsgUpdateClassFees(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, govk ecocredit.GovKeeper, + qryClient types.QueryServer, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgUpdateClassFees) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateClassFees, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateClassFees, "unable to generate deposit"), nil, err + } + + fees := utils.RandomFees(r) + proposalMsg := types.MsgUpdateClassFees{ + Authority: authority.String(), + Fees: fees, + } + + any, err := codectypes.NewAnyWithValue(&proposalMsg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateClassFees, err.Error()), nil, err + } + + msg := &govtypes.MsgSubmitProposal{ + Messages: []*codectypes.Any{any}, + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + + return false +} + func getClassIssuers(ctx sdk.Context, qryClient types.QueryServer, className string, msgType string) ([]string, simtypes.OperationMsg, error) { classIssuers, err := qryClient.ClassIssuers(sdk.WrapSDKContext(ctx), &types.QueryClassIssuersRequest{ClassId: className}) if err != nil { diff --git a/x/ecocredit/base/types/v1/codec.go b/x/ecocredit/base/types/v1/codec.go index 89034da928..3093f48db1 100644 --- a/x/ecocredit/base/types/v1/codec.go +++ b/x/ecocredit/base/types/v1/codec.go @@ -33,6 +33,10 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&CreditTypeProposal{}, "regen/CreditTypeProposal", nil) cdc.RegisterConcrete(&MsgBridgeReceive{}, "regen/MsgBridgeReceive", nil) cdc.RegisterConcrete(&MsgAddCreditType{}, "regen/MsgAddCreditType", nil) + cdc.RegisterConcrete(&MsgAddClassCreator{}, "regen/MsgAddClassCreator", nil) + cdc.RegisterConcrete(&MsgRemoveClassCreator{}, "regen/MsgRemoveClassCreator", nil) + cdc.RegisterConcrete(&MsgSetClassCreatorAllowlist{}, "regen/MsgSetClassCreatorAllowlist", nil) + cdc.RegisterConcrete(&MsgUpdateClassFees{}, "regen/MsgUpdateClassFees", nil) } var ( diff --git a/x/ecocredit/basket/simulation/msg_create.go b/x/ecocredit/basket/simulation/msg_create.go new file mode 100644 index 0000000000..0fe3a69f65 --- /dev/null +++ b/x/ecocredit/basket/simulation/msg_create.go @@ -0,0 +1,197 @@ +package simulation + +import ( + "context" + "fmt" + "math/rand" + "strings" + + gogotypes "github.com/gogo/protobuf/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/orm/types/ormerrors" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/regen-network/regen-ledger/x/ecocredit" + basetypes "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" + "github.com/regen-network/regen-ledger/x/ecocredit/basket" + types "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" + "github.com/regen-network/regen-ledger/x/ecocredit/simulation/utils" +) + +const WeightCreate = 100 + +var TypeMsgCreate = types.MsgCreate{}.Route() + +const OpWeightMsgCreate = "op_weight_msg_create_basket" //nolint:gosec + +// SimulateMsgCreate generates a Basket/MsgCreate with random values. +func SimulateMsgCreate(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + baseClient basetypes.QueryServer, client types.QueryServer) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + curator, _ := simtypes.RandomAcc(r, accs) + + ctx := sdk.WrapSDKContext(sdkCtx) + res, err := baseClient.Params(ctx, &basetypes.QueryParamsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, err + } + + params := res.Params + spendable := bk.SpendableCoins(sdkCtx, curator.Address) + if !spendable.IsAllGTE(params.BasketFee) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "not enough balance"), nil, nil + } + + creditType, err := randomCreditType(ctx, r, baseClient) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, err + } + + if creditType == nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "credit type not found"), nil, nil + } + + classIDs, op, err := randomClassIds(r, sdkCtx, baseClient, creditType.Abbreviation, TypeMsgPut) + if len(classIDs) == 0 { + return op, nil, err + } + + precision := creditType.Precision + exponent := utils.RandomExponent(r, precision) + basketName := simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 3, 8)) + denom, _, err := basket.FormatBasketDenom(basketName, creditType.Abbreviation, exponent) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "failed to generate basket denom"), nil, err + } + + result, err := client.Basket(sdkCtx, &types.QueryBasketRequest{ + BasketDenom: denom, + }) + if err != nil && !ormerrors.NotFound.Is(err) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, err + } + + if result != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, fmt.Sprintf("basket with name %s already exists", basketName)), nil, nil + } + + dateCriteria := randomDateCriteria(r, sdkCtx) + msg := &types.MsgCreate{ + Name: basketName, + Description: simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 3, 256)), + Fee: params.BasketFee, + DisableAutoRetire: r.Float32() < 0.5, + Curator: curator.Address.String(), + Exponent: exponent, + AllowedClasses: classIDs, + CreditTypeAbbrev: creditType.Abbreviation, + DateCriteria: dateCriteria, + } + + fees, err := simtypes.RandomFees(r, sdkCtx, spendable) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "fee error"), nil, err + } + + account := ak.GetAccount(sdkCtx, curator.Address) + txGen := simappparams.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + 2000000, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + curator.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "unable to generate mock tx"), nil, err + } + + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + if strings.Contains(err.Error(), "basket specified credit type") { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, nil + } + + if strings.Contains(err.Error(), "insufficient funds") { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, nil + } + return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil + + } +} + +func randomCreditType(ctx context.Context, r *rand.Rand, qryClient basetypes.QueryServer) (*basetypes.CreditType, error) { + res, err := qryClient.CreditTypes(ctx, &basetypes.QueryCreditTypesRequest{}) + if err != nil { + return nil, err + } + + creditTypes := res.CreditTypes + if len(creditTypes) == 0 { + return nil, nil + } + + return creditTypes[r.Intn(len(creditTypes))], nil +} + +func randomDateCriteria(r *rand.Rand, ctx sdk.Context) *types.DateCriteria { + // 30% chance of date-criteria being enable + includeCriteria := r.Int63n(101) <= 30 + if includeCriteria { + seconds := ctx.BlockTime().AddDate(0, -1, 0).Unix() + if r.Float32() < 0.5 { + return &types.DateCriteria{ + MinStartDate: &gogotypes.Timestamp{ + Seconds: seconds, + }, + } + } + return &types.DateCriteria{ + StartDateWindow: &gogotypes.Duration{Seconds: seconds}, + } + } + return nil +} + +func randomClassIds(r *rand.Rand, ctx sdk.Context, qryClient basetypes.QueryServer, + creditTypeAbbrev string, msgType string) ([]string, simtypes.OperationMsg, error) { + classes, op, err := utils.GetClasses(ctx, r, qryClient, msgType) + if len(classes) == 0 { + return []string{}, op, err + } + + if len(classes) == 1 { + return []string{classes[0].Id}, simtypes.NoOpMsg(ecocredit.ModuleName, msgType, ""), nil + } + + max := simtypes.RandIntBetween(r, 1, min(5, len(classes))) + var classIDs []string + for i := 0; i < max; i++ { + class := classes[i] + if class.CreditTypeAbbrev == creditTypeAbbrev { + classIDs = append(classIDs, class.Id) + } + } + + return classIDs, simtypes.NoOpMsg(ecocredit.ModuleName, msgType, ""), nil +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} diff --git a/x/ecocredit/basket/simulation/msg_put.go b/x/ecocredit/basket/simulation/msg_put.go new file mode 100644 index 0000000000..cc58b57e87 --- /dev/null +++ b/x/ecocredit/basket/simulation/msg_put.go @@ -0,0 +1,202 @@ +package simulation + +import ( + "fmt" + "math/rand" + "strings" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/regen-network/regen-ledger/types/math" + "github.com/regen-network/regen-ledger/x/ecocredit" + + basetypes "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" + types "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" + "github.com/regen-network/regen-ledger/x/ecocredit/simulation/utils" +) + +const WeightPut = 100 + +const OpWeightMsgPut = "op_weight_msg_put_into_basket" //nolint:gosec + +var TypeMsgPut = types.MsgPut{}.Route() + +// SimulateMsgPut generates a Basket/MsgPut with random values. +func SimulateMsgPut(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + qryClient basetypes.QueryServer, bsktQryClient types.QueryServer) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + ctx := sdk.WrapSDKContext(sdkCtx) + res, err := bsktQryClient.Baskets(ctx, &types.QueryBasketsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + + baskets := res.Baskets + if len(baskets) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no baskets"), nil, nil + } + + classes, op, err := utils.GetClasses(sdkCtx, r, qryClient, TypeMsgPut) + if len(classes) == 0 { + return op, nil, err + } + + rBasket := baskets[r.Intn(len(baskets))] + var classInfoList []basetypes.ClassInfo + max := 0 + + var ownerAddr string + var owner simtypes.Account + for _, class := range classes { + if class.CreditTypeAbbrev == rBasket.CreditTypeAbbrev { + issuersRes, err := qryClient.ClassIssuers(sdk.WrapSDKContext(sdkCtx), &basetypes.QueryClassIssuersRequest{ + ClassId: class.Id, + }) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + issuers := issuersRes.Issuers + if len(issuers) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no class issuers"), nil, nil + } + + if ownerAddr == "" { + bechAddr, err := sdk.AccAddressFromBech32(issuers[0]) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + + acc, found := simtypes.FindAccount(accs, bechAddr) + if found { + ownerAddr = issuers[0] + owner = acc + classInfoList = append(classInfoList, *class) + max++ + } + } else if utils.Contains(issuers, ownerAddr) { + classInfoList = append(classInfoList, *class) + max++ + } + + if max == 2 { + break + } + } + } + if len(classInfoList) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no classes"), nil, nil + } + + var credits []*types.BasketCredit + for _, classInfo := range classInfoList { + + resProjects, err := qryClient.ProjectsByClass(ctx, &basetypes.QueryProjectsByClassRequest{ClassId: classInfo.Id}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + + for _, projectInfo := range resProjects.GetProjects() { + + batchesRes, err := qryClient.BatchesByProject(ctx, &basetypes.QueryBatchesByProjectRequest{ + ProjectId: projectInfo.Id, + }) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + + batches := batchesRes.Batches + if len(batches) != 0 { + count := 0 + for _, batch := range batches { + balanceRes, err := qryClient.Balance(ctx, &basetypes.QueryBalanceRequest{ + Address: ownerAddr, BatchDenom: batch.Denom, + }) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err + } + + tradableAmount := balanceRes.Balance.TradableAmount + if tradableAmount != "0" { + d, err := math.NewPositiveDecFromString(tradableAmount) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, nil + } + + dInt, err := d.Int64() + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, nil + } + + if dInt == 1 { + credits = append(credits, &types.BasketCredit{ + BatchDenom: batch.Denom, + Amount: "1", + }) + count++ + } else { + amt := simtypes.RandIntBetween(r, 1, int(dInt)) + credits = append(credits, &types.BasketCredit{ + BatchDenom: batch.Denom, + Amount: fmt.Sprintf("%d", amt), + }) + count++ + } + } + + if count == 3 { + break + } + } + } + } + } + if len(credits) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no basket credits"), nil, nil + } + + msg := &types.MsgPut{ + Owner: owner.Address.String(), + BasketDenom: rBasket.BasketDenom, + Credits: credits, + } + spendable := bk.SpendableCoins(sdkCtx, owner.Address) + fees, err := simtypes.RandomFees(r, sdkCtx, spendable) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "fee error"), nil, err + } + + account := ak.GetAccount(sdkCtx, owner.Address) + txGen := simappparams.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + 2000000, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + owner.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "unable to generate mock tx"), nil, err + } + + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + if strings.Contains(err.Error(), "is not allowed in this basket") { + return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "class is not allowed"), nil, nil + } + + return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil + } +} diff --git a/x/ecocredit/basket/simulation/msg_take.go b/x/ecocredit/basket/simulation/msg_take.go new file mode 100644 index 0000000000..c7dcafec6e --- /dev/null +++ b/x/ecocredit/basket/simulation/msg_take.go @@ -0,0 +1,111 @@ +package simulation + +import ( + "fmt" + "math/rand" + "strconv" + + "github.com/cosmos/cosmos-sdk/baseapp" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/regen-network/regen-ledger/x/ecocredit" + basetypes "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" + types "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" + "github.com/regen-network/regen-ledger/x/ecocredit/simulation/utils" +) + +const OpWeightMsgTake = "op_weight_take_from_basket" //nolint:gosec + +const WeightTake = 100 + +var TypeMsgTake = types.MsgTake{}.Route() + +// SimulateMsgTake generates a Basket/MsgTake with random values. +func SimulateMsgTake(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + qryClient basetypes.QueryServer, bsktQryClient types.QueryServer) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + owner, _ := simtypes.RandomAcc(r, accs) + ownerAddr := owner.Address.String() + + ctx := sdk.WrapSDKContext(sdkCtx) + res, err := bsktQryClient.Baskets(ctx, &types.QueryBasketsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, err + } + + baskets := res.BasketsInfo + if len(baskets) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "no baskets"), nil, nil + } + + var rBasket *types.BasketInfo + var bBalances []*types.BasketBalanceInfo + for _, b := range baskets { + balancesRes, err := bsktQryClient.BasketBalances(ctx, &types.QueryBasketBalancesRequest{ + BasketDenom: b.BasketDenom, + }) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, err + } + balances := balancesRes.BalancesInfo + if len(balances) != 0 { + rBasket = b + bBalances = balances + break + } + } + if rBasket == nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "no basket"), nil, nil + } + + var amt int + for _, b := range bBalances { + iAmount, err := strconv.Atoi(b.Balance) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, nil + } + + switch { + case iAmount == 0: + continue + case iAmount == 1: + amt = iAmount + default: + amt = simtypes.RandIntBetween(r, 1, iAmount) + } + } + if amt == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "basket balance"), nil, nil + } + + msg := &types.MsgTake{ + Owner: ownerAddr, + BasketDenom: rBasket.BasketDenom, + Amount: fmt.Sprintf("%d", amt), + RetirementJurisdiction: "AQ", + RetireOnTake: !rBasket.DisableAutoRetire, + } + + spendable := bk.SpendableCoins(sdkCtx, owner.Address) + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: owner, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} diff --git a/x/ecocredit/basket/simulation/msg_update_basket_fees.go b/x/ecocredit/basket/simulation/msg_update_basket_fees.go new file mode 100644 index 0000000000..9dbc106516 --- /dev/null +++ b/x/ecocredit/basket/simulation/msg_update_basket_fees.go @@ -0,0 +1,85 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/regen-network/regen-ledger/x/ecocredit" + basetypes "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" + types "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" + "github.com/regen-network/regen-ledger/x/ecocredit/simulation/utils" +) + +const OpWeightMsgUpdateBasketFees = "op_weight_msg_update_basket_fees" //nolint:gosec + +var TypeMsgUpdateBasketFees = types.MsgUpdateBasketFees{}.Route() + +const WeightUpdateBasketFees = 100 + +// SimulateMsgCreate generates a Basket/MsgUpdateBasketFees with random values. +func SimulateMsgUpdateBasketFees(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, qryClient basetypes.QueryServer, + basketQryClient types.QueryServer, govk ecocredit.GovKeeper, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgUpdateBasketFees) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, proposer.Address) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateBasketFees, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateBasketFees, "unable to generate deposit"), nil, err + } + + fees := utils.RandomFees(r) + msg := types.MsgUpdateBasketFees{ + Authority: authority.String(), + BasketFees: fees, + } + + any, err := codectypes.NewAnyWithValue(&msg) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgUpdateBasketFees, err.Error()), nil, err + } + + proposalMsg := govtypes.MsgSubmitProposal{ + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + Messages: []*codectypes.Any{any}, + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simapp.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &proposalMsg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} diff --git a/x/ecocredit/basket/simulation/operations.go b/x/ecocredit/basket/simulation/operations.go index d0e64e7a4d..5b3ac85abe 100644 --- a/x/ecocredit/basket/simulation/operations.go +++ b/x/ecocredit/basket/simulation/operations.go @@ -1,59 +1,30 @@ -package basketsims +package simulation import ( - "context" - "fmt" "math/rand" - "strconv" - "strings" - gogotypes "github.com/gogo/protobuf/types" - - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp/helpers" - simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/regen-network/regen-ledger/types/math" "github.com/regen-network/regen-ledger/x/ecocredit" basetypes "github.com/regen-network/regen-ledger/x/ecocredit/base/types/v1" types "github.com/regen-network/regen-ledger/x/ecocredit/basket/types/v1" - "github.com/regen-network/regen-ledger/x/ecocredit/simulation/utils" -) - -// Simulation operation weights constants -const ( - OpWeightMsgCreate = "op_weight_msg_create_basket" //nolint:gosec - OpWeightMsgPut = "op_weight_msg_put_into_basket" //nolint:gosec - OpWeightMsgTake = "op_weight_take_from_basket" //nolint:gosec -) - -// basket operations weights -const ( - WeightCreate = 100 - WeightPut = 100 - WeightTake = 100 -) - -// ecocredit message types -var ( - TypeMsgCreate = types.MsgCreate{}.Route() - TypeMsgPut = types.MsgPut{}.Route() - TypeMsgTake = types.MsgTake{}.Route() ) func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, - qryClient basetypes.QueryServer, basketQryClient types.QueryServer) simulation.WeightedOperations { + govk ecocredit.GovKeeper, + qryClient basetypes.QueryServer, basketQryClient types.QueryServer, + authority sdk.AccAddress) simulation.WeightedOperations { var ( - weightMsgCreate int - weightMsgPut int - weightMsgTake int + weightMsgCreate int + weightMsgPut int + weightMsgTake int + weightMsgUpdateBasketFees int ) appParams.GetOrGenerate(cdc, OpWeightMsgCreate, &weightMsgCreate, nil, @@ -74,10 +45,16 @@ func WeightedOperations( }, ) + appParams.GetOrGenerate(cdc, OpWeightMsgUpdateBasketFees, &weightMsgUpdateBasketFees, nil, + func(_ *rand.Rand) { + weightMsgUpdateBasketFees = WeightUpdateBasketFees + }, + ) + return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgCreate, - SimulateMsgCreate(ak, bk, qryClient), + SimulateMsgCreate(ak, bk, qryClient, basketQryClient), ), simulation.NewWeightedOperation( weightMsgPut, @@ -87,419 +64,9 @@ func WeightedOperations( weightMsgTake, SimulateMsgTake(ak, bk, qryClient, basketQryClient), ), + simulation.NewWeightedOperation( + weightMsgUpdateBasketFees, + SimulateMsgUpdateBasketFees(ak, bk, qryClient, basketQryClient, govk, authority), + ), } } - -// SimulateMsgCreate generates a Basket/MsgCreate with random values. -func SimulateMsgCreate(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, - qryClient basetypes.QueryServer) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - curator, _ := simtypes.RandomAcc(r, accs) - - ctx := sdk.WrapSDKContext(sdkCtx) - res, err := qryClient.Params(ctx, &basetypes.QueryParamsRequest{}) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, err - } - - params := res.Params - spendable := bk.SpendableCoins(sdkCtx, curator.Address) - if !spendable.IsAllGTE(params.BasketFee) { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "not enough balance"), nil, nil - } - - creditType, err := randomCreditType(ctx, r, qryClient) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, err - } - - if creditType == nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "credit type not found"), nil, nil - } - - classIDs, op, err := randomClassIds(r, sdkCtx, qryClient, creditType.Abbreviation, TypeMsgPut) - if len(classIDs) == 0 { - return op, nil, err - } - - precision := creditType.Precision - dateCriteria := randomDateCriteria(r, sdkCtx) - msg := &types.MsgCreate{ - Name: simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 3, 8)), - Description: simtypes.RandStringOfLength(r, simtypes.RandIntBetween(r, 3, 256)), - Fee: params.BasketFee, - DisableAutoRetire: r.Float32() < 0.5, - Curator: curator.Address.String(), - Exponent: utils.RandomExponent(r, precision), - AllowedClasses: classIDs, - CreditTypeAbbrev: creditType.Abbreviation, - DateCriteria: dateCriteria, - } - - fees, err := simtypes.RandomFees(r, sdkCtx, spendable) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "fee error"), nil, err - } - - account := ak.GetAccount(sdkCtx, curator.Address) - txGen := simappparams.MakeTestEncodingConfig().TxConfig - tx, err := helpers.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - 2000000, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - curator.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - if strings.Contains(err.Error(), "basket specified credit type") { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, nil - } - - if strings.Contains(err.Error(), "insufficient funds") { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgCreate, err.Error()), nil, nil - } - return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil - - } -} - -func randomDateCriteria(r *rand.Rand, ctx sdk.Context) *types.DateCriteria { - // 30% chance of date-criteria being enable - includeCriteria := r.Int63n(101) <= 30 - if includeCriteria { - seconds := ctx.BlockTime().AddDate(0, -1, 0).Unix() - if r.Float32() < 0.5 { - return &types.DateCriteria{ - MinStartDate: &gogotypes.Timestamp{ - Seconds: seconds, - }, - } - } - return &types.DateCriteria{ - StartDateWindow: &gogotypes.Duration{Seconds: seconds}, - } - } - return nil -} - -// SimulateMsgPut generates a Basket/MsgPut with random values. -func SimulateMsgPut(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, - qryClient basetypes.QueryServer, bsktQryClient types.QueryServer) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - ctx := sdk.WrapSDKContext(sdkCtx) - res, err := bsktQryClient.Baskets(ctx, &types.QueryBasketsRequest{}) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - - baskets := res.Baskets - if len(baskets) == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no baskets"), nil, nil - } - - classes, op, err := utils.GetClasses(sdkCtx, r, qryClient, TypeMsgPut) - if len(classes) == 0 { - return op, nil, err - } - - rBasket := baskets[r.Intn(len(baskets))] - var classInfoList []basetypes.ClassInfo - max := 0 - - var ownerAddr string - var owner simtypes.Account - for _, class := range classes { - if class.CreditTypeAbbrev == rBasket.CreditTypeAbbrev { - issuersRes, err := qryClient.ClassIssuers(sdk.WrapSDKContext(sdkCtx), &basetypes.QueryClassIssuersRequest{ - ClassId: class.Id, - }) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - issuers := issuersRes.Issuers - if len(issuers) == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no class issuers"), nil, nil - } - - if ownerAddr == "" { - bechAddr, err := sdk.AccAddressFromBech32(issuers[0]) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - - acc, found := simtypes.FindAccount(accs, bechAddr) - if found { - ownerAddr = issuers[0] - owner = acc - classInfoList = append(classInfoList, *class) - max++ - } - } else if utils.Contains(issuers, ownerAddr) { - classInfoList = append(classInfoList, *class) - max++ - } - - if max == 2 { - break - } - } - } - if len(classInfoList) == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no classes"), nil, nil - } - - var credits []*types.BasketCredit - for _, classInfo := range classInfoList { - - resProjects, err := qryClient.ProjectsByClass(ctx, &basetypes.QueryProjectsByClassRequest{ClassId: classInfo.Id}) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - - for _, projectInfo := range resProjects.GetProjects() { - - batchesRes, err := qryClient.BatchesByProject(ctx, &basetypes.QueryBatchesByProjectRequest{ - ProjectId: projectInfo.Id, - }) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - - batches := batchesRes.Batches - if len(batches) != 0 { - count := 0 - for _, batch := range batches { - balanceRes, err := qryClient.Balance(ctx, &basetypes.QueryBalanceRequest{ - Address: ownerAddr, BatchDenom: batch.Denom, - }) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, err - } - - tradableAmount := balanceRes.Balance.TradableAmount - if tradableAmount != "0" { - d, err := math.NewPositiveDecFromString(tradableAmount) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, nil - } - - dInt, err := d.Int64() - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, err.Error()), nil, nil - } - - if dInt == 1 { - credits = append(credits, &types.BasketCredit{ - BatchDenom: batch.Denom, - Amount: "1", - }) - count++ - } else { - amt := simtypes.RandIntBetween(r, 1, int(dInt)) - credits = append(credits, &types.BasketCredit{ - BatchDenom: batch.Denom, - Amount: fmt.Sprintf("%d", amt), - }) - count++ - } - } - - if count == 3 { - break - } - } - } - } - } - if len(credits) == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "no basket credits"), nil, nil - } - - msg := &types.MsgPut{ - Owner: owner.Address.String(), - BasketDenom: rBasket.BasketDenom, - Credits: credits, - } - spendable := bk.SpendableCoins(sdkCtx, owner.Address) - fees, err := simtypes.RandomFees(r, sdkCtx, spendable) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "fee error"), nil, err - } - - account := ak.GetAccount(sdkCtx, owner.Address) - txGen := simappparams.MakeTestEncodingConfig().TxConfig - tx, err := helpers.GenSignedMockTx( - r, - txGen, - []sdk.Msg{msg}, - fees, - 2000000, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - owner.PrivKey, - ) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgPut, "unable to generate mock tx"), nil, err - } - - _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) - if err != nil { - if strings.Contains(err.Error(), "is not allowed in this basket") { - return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "class is not allowed"), nil, nil - } - - return simtypes.NoOpMsg(ecocredit.ModuleName, msg.Type(), "unable to deliver tx"), nil, err - } - - return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil - } -} - -// SimulateMsgTake generates a Basket/MsgTake with random values. -func SimulateMsgTake(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, - qryClient basetypes.QueryServer, bsktQryClient types.QueryServer) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - owner, _ := simtypes.RandomAcc(r, accs) - ownerAddr := owner.Address.String() - - ctx := sdk.WrapSDKContext(sdkCtx) - res, err := bsktQryClient.Baskets(ctx, &types.QueryBasketsRequest{}) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, err - } - - baskets := res.BasketsInfo - if len(baskets) == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "no baskets"), nil, nil - } - - var rBasket *types.BasketInfo - var bBalances []*types.BasketBalanceInfo - for _, b := range baskets { - balancesRes, err := bsktQryClient.BasketBalances(ctx, &types.QueryBasketBalancesRequest{ - BasketDenom: b.BasketDenom, - }) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, err - } - balances := balancesRes.BalancesInfo - if len(balances) != 0 { - rBasket = b - bBalances = balances - break - } - } - if rBasket == nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "no basket"), nil, nil - } - - var amt int - for _, b := range bBalances { - iAmount, err := strconv.Atoi(b.Balance) - if err != nil { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, err.Error()), nil, nil - } - - switch { - case iAmount == 0: - continue - case iAmount == 1: - amt = iAmount - default: - amt = simtypes.RandIntBetween(r, 1, iAmount) - } - } - if amt == 0 { - return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgTake, "basket balance"), nil, nil - } - - msg := &types.MsgTake{ - Owner: ownerAddr, - BasketDenom: rBasket.BasketDenom, - Amount: fmt.Sprintf("%d", amt), - RetirementJurisdiction: "AQ", - RetireOnTake: !rBasket.DisableAutoRetire, - } - - spendable := bk.SpendableCoins(sdkCtx, owner.Address) - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: simappparams.MakeTestEncodingConfig().TxConfig, - Cdc: nil, - Msg: msg, - MsgType: msg.Type(), - Context: sdkCtx, - SimAccount: owner, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: ecocredit.ModuleName, - CoinsSpentInMsg: spendable, - } - - return utils.GenAndDeliverTxWithRandFees(r, txCtx) - } -} - -func randomClassIds(r *rand.Rand, ctx sdk.Context, qryClient basetypes.QueryServer, - creditTypeAbbrev string, msgType string) ([]string, simtypes.OperationMsg, error) { - classes, op, err := utils.GetClasses(ctx, r, qryClient, msgType) - if len(classes) == 0 { - return []string{}, op, err - } - - if len(classes) == 1 { - return []string{classes[0].Id}, simtypes.NoOpMsg(ecocredit.ModuleName, msgType, ""), nil - } - - max := simtypes.RandIntBetween(r, 1, min(5, len(classes))) - var classIDs []string - for i := 0; i < max; i++ { - class := classes[i] - if class.CreditTypeAbbrev == creditTypeAbbrev { - classIDs = append(classIDs, class.Id) - } - } - - return classIDs, simtypes.NoOpMsg(ecocredit.ModuleName, msgType, ""), nil -} - -func min(x, y int) int { - if x > y { - return y - } - return x -} - -func randomCreditType(ctx context.Context, r *rand.Rand, qryClient basetypes.QueryServer) (*basetypes.CreditType, error) { - res, err := qryClient.CreditTypes(ctx, &basetypes.QueryCreditTypesRequest{}) - if err != nil { - return nil, err - } - - creditTypes := res.CreditTypes - if len(creditTypes) == 0 { - return nil, nil - } - - return creditTypes[r.Intn(len(creditTypes))], nil -} diff --git a/x/ecocredit/expected_keepers.go b/x/ecocredit/expected_keepers.go index 957693f916..fd19b4a9f5 100644 --- a/x/ecocredit/expected_keepers.go +++ b/x/ecocredit/expected_keepers.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/params/types" ) @@ -37,6 +38,12 @@ type BankKeeper interface { GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin } +// GovKeeper defines the expected interface needed to query governance params +type GovKeeper interface { + // GetDepositParams queries governance module deposit params + GetDepositParams(ctx sdk.Context) govv1.DepositParams +} + type ParamKeeper interface { // Get fetches a parameter by key from the Subspace's KVStore and sets the provided pointer to the fetched value. diff --git a/x/ecocredit/marketplace/simulation/operations.go b/x/ecocredit/marketplace/simulation/operations.go index 1741e291dc..711287f06f 100644 --- a/x/ecocredit/marketplace/simulation/operations.go +++ b/x/ecocredit/marketplace/simulation/operations.go @@ -7,9 +7,11 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/regen-network/regen-ledger/types/math" @@ -21,38 +23,47 @@ import ( // Simulation operation weights constants const ( - OpWeightMsgBuy = "op_weight_msg_buy_direct" //nolint:gosec - OpWeightMsgSell = "op_weight_msg_sell" //nolint:gosec - OpWeightMsgUpdateSellOrder = "op_weight_msg_update_sell_order" //nolint:gosec - OpWeightMsgCancelSellOrder = "op_weight_msg_cancel_sell_order" //nolint:gosec + OpWeightMsgBuy = "op_weight_msg_buy_direct" //nolint:gosec + OpWeightMsgSell = "op_weight_msg_sell" //nolint:gosec + OpWeightMsgUpdateSellOrder = "op_weight_msg_update_sell_order" //nolint:gosec + OpWeightMsgCancelSellOrder = "op_weight_msg_cancel_sell_order" //nolint:gosec + OpWeightMsgAddAllowedDenom = "op_weight_msg_add_allowed_denom" //nolint:gosec + OpWeightMsgRemoveAllowedDenom = "op_weight_msg_remove_allowed_denom" //nolint:gosec ) // basket operations weights const ( - WeightBuyDirect = 100 - WeightSell = 100 - WeightUpdateSellOrder = 100 - WeightCancelSellOrder = 100 + WeightBuyDirect = 100 + WeightSell = 100 + WeightUpdateSellOrder = 100 + WeightCancelSellOrder = 100 + WeightAddAllowedDenom = 100 + WeightRemoveAllowedDenom = 100 ) // ecocredit message types var ( - TypeMsgBuyDirect = types.MsgBuyDirect{}.Route() - TypeMsgSell = types.MsgSell{}.Route() - TypeMsgUpdateSellOrder = types.MsgUpdateSellOrders{}.Route() - TypeMsgCancelSellOrder = types.MsgCancelSellOrder{}.Route() + TypeMsgBuyDirect = types.MsgBuyDirect{}.Route() + TypeMsgSell = types.MsgSell{}.Route() + TypeMsgUpdateSellOrder = types.MsgUpdateSellOrders{}.Route() + TypeMsgCancelSellOrder = types.MsgCancelSellOrder{}.Route() + TypeMsgAddAllowedDenom = types.MsgAddAllowedDenom{}.Route() + TypeMsgRemoveAllowedDenom = types.MsgRemoveAllowedDenom{}.Route() ) func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, - qryClient basetypes.QueryServer, mktQryClient types.QueryServer) simulation.WeightedOperations { + qryClient basetypes.QueryServer, mktQryClient types.QueryServer, + govk ecocredit.GovKeeper, authority sdk.AccAddress) simulation.WeightedOperations { var ( - weightMsgBuyDirect int - weightMsgSell int - weightMsgUpdateSellOrder int - weightMsgCancelSellOrder int + weightMsgBuyDirect int + weightMsgSell int + weightMsgUpdateSellOrder int + weightMsgCancelSellOrder int + weightMsgAddAllowedDenom int + weightMsgRemoveAllowedDenom int ) appParams.GetOrGenerate(cdc, OpWeightMsgBuy, &weightMsgBuyDirect, nil, @@ -79,6 +90,18 @@ func WeightedOperations( }, ) + appParams.GetOrGenerate(cdc, OpWeightMsgAddAllowedDenom, &weightMsgAddAllowedDenom, nil, + func(_ *rand.Rand) { + weightMsgAddAllowedDenom = WeightAddAllowedDenom + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgRemoveAllowedDenom, &weightMsgRemoveAllowedDenom, nil, + func(_ *rand.Rand) { + weightMsgRemoveAllowedDenom = WeightRemoveAllowedDenom + }, + ) + return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgBuyDirect, @@ -96,6 +119,14 @@ func WeightedOperations( weightMsgCancelSellOrder, SimulateMsgCancelSellOrder(ak, bk, qryClient, mktQryClient), ), + simulation.NewWeightedOperation( + weightMsgAddAllowedDenom, + SimulateMsgAddAllowedDenom(ak, bk, mktQryClient, govk, authority), + ), + simulation.NewWeightedOperation( + weightMsgRemoveAllowedDenom, + SimulateMsgRemoveAllowedDenom(ak, bk, mktQryClient, govk, authority), + ), } } @@ -401,3 +432,152 @@ func SimulateMsgCancelSellOrder(ak ecocredit.AccountKeeper, bk ecocredit.BankKee return utils.GenAndDeliverTxWithRandFees(r, txCtx) } } + +func SimulateMsgAddAllowedDenom(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + qryClient types.QueryServer, govk ecocredit.GovKeeper, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgAddAllowedDenom) + if spendable == nil { + return op, nil, err + } + + bankDenom := simtypes.RandStringOfLength(r, 4) + res, err := qryClient.AllowedDenoms(sdkCtx, &types.QueryAllowedDenomsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddAllowedDenom, err.Error()), nil, err + } + + if isDenomExists(res.AllowedDenoms, bankDenom) { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddAllowedDenom, fmt.Sprintf("denom %s already exists", bankDenom)), nil, nil + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, authority) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddAllowedDenom, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgAddAllowedDenom, "unable to generate deposit"), nil, err + } + + msg := types.MsgAddAllowedDenom{ + Authority: authority.String(), + BankDenom: bankDenom, + DisplayDenom: bankDenom, + Exponent: 6, + } + + any, err := codectypes.NewAnyWithValue(&msg) + if err != nil { + return simtypes.NoOpMsg(TypeMsgAddAllowedDenom, TypeMsgAddAllowedDenom, err.Error()), nil, err + } + + proposalMsg := govtypes.MsgSubmitProposal{ + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + Messages: []*codectypes.Any{any}, + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simapp.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &proposalMsg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} + +func isDenomExists(allowedDenom []*types.AllowedDenom, bankDenom string) bool { + for _, denom := range allowedDenom { + if denom.BankDenom == bankDenom { + return true + } + } + + return false +} + +func SimulateMsgRemoveAllowedDenom(ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, + mktClient types.QueryServer, govk ecocredit.GovKeeper, authority sdk.AccAddress) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + response, err := mktClient.AllowedDenoms(sdkCtx, &types.QueryAllowedDenomsRequest{}) + if err != nil { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveAllowedDenom, err.Error()), nil, err + } + + if len(response.AllowedDenoms) == 0 { + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveAllowedDenom, "no allowed denom present"), nil, nil + } + + proposer, _ := simtypes.RandomAcc(r, accs) + proposerAddr := proposer.Address.String() + + spendable, account, op, err := utils.GetAccountAndSpendableCoins(sdkCtx, bk, accs, proposerAddr, TypeMsgRemoveAllowedDenom) + if spendable == nil { + return op, nil, err + } + + params := govk.GetDepositParams(sdkCtx) + deposit, skip, err := utils.RandomDeposit(r, sdkCtx, ak, bk, params, authority) + switch { + case skip: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveAllowedDenom, "skip deposit"), nil, nil + case err != nil: + return simtypes.NoOpMsg(ecocredit.ModuleName, TypeMsgRemoveAllowedDenom, "unable to generate deposit"), nil, err + } + + msg := types.MsgRemoveAllowedDenom{ + Authority: authority.String(), + Denom: response.AllowedDenoms[r.Intn(len(response.AllowedDenoms))].BankDenom, + } + + any, err := codectypes.NewAnyWithValue(&msg) + if err != nil { + return simtypes.NoOpMsg(TypeMsgAddAllowedDenom, TypeMsgAddAllowedDenom, err.Error()), nil, err + } + + proposalMsg := govtypes.MsgSubmitProposal{ + InitialDeposit: deposit, + Proposer: proposerAddr, + Metadata: simtypes.RandStringOfLength(r, 10), + Messages: []*codectypes.Any{any}, + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simapp.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &proposalMsg, + MsgType: msg.Type(), + Context: sdkCtx, + SimAccount: *account, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: ecocredit.ModuleName, + CoinsSpentInMsg: spendable, + } + + return utils.GenAndDeliverTxWithRandFees(r, txCtx) + } +} diff --git a/x/ecocredit/module/module.go b/x/ecocredit/module/module.go index a68ffa597d..6d8c8d6593 100644 --- a/x/ecocredit/module/module.go +++ b/x/ecocredit/module/module.go @@ -54,6 +54,7 @@ type Module struct { Keeper server.Keeper accountKeeper ecocredit.AccountKeeper bankKeeper ecocredit.BankKeeper + govKeeper ecocredit.GovKeeper // legacySubspace is used solely for migration of x/ecocredit managed parameters legacySubspace paramtypes.Subspace @@ -66,6 +67,7 @@ func NewModule( accountKeeper ecocredit.AccountKeeper, bankKeeper ecocredit.BankKeeper, legacySubspace paramtypes.Subspace, + govKeeper ecocredit.GovKeeper, ) *Module { // legacySubspace is used solely for migration of x/ecocredit managed parameters @@ -79,6 +81,7 @@ func NewModule( bankKeeper: bankKeeper, accountKeeper: accountKeeper, authority: authority, + govKeeper: govKeeper, } } @@ -307,8 +310,10 @@ func (m Module) WeightedOperations(simState module.SimulationState) []simtypes.W simState.Cdc, m.accountKeeper, m.bankKeeper, + m.govKeeper, baseServer, basketServer, marketServer, + m.authority, ) } diff --git a/x/ecocredit/server/server_test.go b/x/ecocredit/server/server_test.go index 2a6f94df5f..6e6a471754 100644 --- a/x/ecocredit/server/server_test.go +++ b/x/ecocredit/server/server_test.go @@ -80,7 +80,7 @@ func setup(t *testing.T) (fixture.Factory, paramstypes.Subspace, bankkeeper.Base ) authority := authtypes.NewModuleAddress(govtypes.ModuleName) - ecocreditModule := module.NewModule(ecoKey, authority, accountKeeper, bankKeeper, ecocreditSubspace) + ecocreditModule := module.NewModule(ecoKey, authority, accountKeeper, bankKeeper, ecocreditSubspace, nil) ff.SetModules([]sdkmodule.AppModule{ecocreditModule}) return ff, ecocreditSubspace, bankKeeper, accountKeeper diff --git a/x/ecocredit/server/tests/utils.go b/x/ecocredit/server/tests/utils.go index cf2e562396..66d3e77e5d 100644 --- a/x/ecocredit/server/tests/utils.go +++ b/x/ecocredit/server/tests/utils.go @@ -61,5 +61,5 @@ func NewEcocreditModule(ff fixture.Factory) *ecocredit.Module { ) _, _, addr := testdata.KeyTestPubAddr() - return ecocredit.NewModule(ecocreditKey, addr, accountKeeper, bankKeeper, ecocreditSubspace) + return ecocredit.NewModule(ecocreditKey, addr, accountKeeper, bankKeeper, ecocreditSubspace, nil) } diff --git a/x/ecocredit/simulation/genesis.go b/x/ecocredit/simulation/genesis.go index f9c40875ad..de677e3e1f 100644 --- a/x/ecocredit/simulation/genesis.go +++ b/x/ecocredit/simulation/genesis.go @@ -3,6 +3,7 @@ package simulation import ( "context" "encoding/json" + "fmt" "math/rand" dbm "github.com/tendermint/tm-db" @@ -17,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + basketapi "github.com/regen-network/regen-ledger/api/regen/ecocredit/basket/v1" marketplaceapi "github.com/regen-network/regen-ledger/api/regen/ecocredit/marketplace/v1" api "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1" "github.com/regen-network/regen-ledger/x/ecocredit" @@ -79,7 +81,12 @@ func RandomizedGenState(simState *module.SimulationState) { panic(err) } - if err := genGenesisState(ormCtx, simState, ss, ms); err != nil { + basketStore, err := basketapi.NewStateStore(ormdb) + if err != nil { + panic(err) + } + + if err := genGenesisState(ormCtx, simState, ss, basketStore, ms); err != nil { panic(err) } @@ -188,7 +195,8 @@ func getBatchSequence(ctx context.Context, sStore api.StateStore, projectKey uin return seq.NextSequence, nil } -func genGenesisState(ctx context.Context, simState *module.SimulationState, ss api.StateStore, ms marketplaceapi.StateStore) error { +func genGenesisState(ctx context.Context, simState *module.SimulationState, ss api.StateStore, + basketStore basketapi.StateStore, ms marketplaceapi.StateStore) error { accs := simState.Accounts r := simState.Rand @@ -246,7 +254,16 @@ func genGenesisState(ctx context.Context, simState *module.SimulationState, ss a } // generate basket params - // TODO: #1363 + if err := basketStore.BasketFeesTable().Save(ctx, &basketapi.BasketFees{ + Fees: []*basev1beta1.Coin{ + { + Denom: sdk.DefaultBondDenom, + Amount: fmt.Sprintf("%d", simtypes.RandIntBetween(r, 10, 100000)), + }, + }, + }); err != nil { + return err + } // generate marketplace params if err := ms.AllowedDenomTable().Insert(ctx, &marketplaceapi.AllowedDenom{ diff --git a/x/ecocredit/simulation/utils/utils.go b/x/ecocredit/simulation/utils/utils.go index 35bb8ee053..c96f671274 100644 --- a/x/ecocredit/simulation/utils/utils.go +++ b/x/ecocredit/simulation/utils/utils.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp/helpers" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/regen-network/regen-ledger/x/ecocredit" @@ -120,3 +121,47 @@ func GetAccountAndSpendableCoins(ctx sdk.Context, bk ecocredit.BankKeeper, spendable := bk.SpendableCoins(ctx, accAddr) return spendable, &account, simtypes.NoOpMsg(ecocredit.ModuleName, msgType, ""), nil } + +// RandomFees generate random credit class/basket creation fees +func RandomFees(r *rand.Rand) sdk.Coins { + coins := sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, simtypes.RandomAmount(r, sdk.NewInt(10000))), + sdk.NewCoin(simtypes.RandStringOfLength(r, 4), simtypes.RandomAmount(r, sdk.NewInt(10000))), + ) + + return coins.Sort() +} + +// RandomDeposit returns minimum deposit if account have enough balance +// else returns deposit amount between (1, balance) +func RandomDeposit(r *rand.Rand, ctx sdk.Context, + ak ecocredit.AccountKeeper, bk ecocredit.BankKeeper, depositParams govtypes.DepositParams, addr sdk.AccAddress, +) (deposit sdk.Coins, skip bool, err error) { + account := ak.GetAccount(ctx, addr) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + if spendable.Empty() { + return nil, true, nil // skip + } + + minDeposit := depositParams.MinDeposit + denomIndex := r.Intn(len(minDeposit)) + denom := minDeposit[denomIndex].Denom + + depositCoins := spendable.AmountOf(denom) + if depositCoins.IsZero() { + return nil, true, nil + } + + amount := depositCoins + if amount.GT(minDeposit[denomIndex].Amount) { + amount = minDeposit[denomIndex].Amount + } else { + amount, err = simtypes.RandPositiveInt(r, depositCoins) + if err != nil { + return nil, false, err + } + } + + return sdk.Coins{sdk.NewCoin(denom, amount)}, false, nil +}