github.com/Finschia/finschia-sdk@v0.48.1/x/staking/simulation/operations.go (about)

     1  package simulation
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  
     7  	"github.com/Finschia/finschia-sdk/baseapp"
     8  	"github.com/Finschia/finschia-sdk/codec"
     9  	simappparams "github.com/Finschia/finschia-sdk/simapp/params"
    10  	sdk "github.com/Finschia/finschia-sdk/types"
    11  	simtypes "github.com/Finschia/finschia-sdk/types/simulation"
    12  	"github.com/Finschia/finschia-sdk/x/simulation"
    13  	"github.com/Finschia/finschia-sdk/x/staking/keeper"
    14  	"github.com/Finschia/finschia-sdk/x/staking/types"
    15  )
    16  
    17  // Simulation operation weights constants
    18  // nolint:gosec
    19  const (
    20  	OpWeightMsgCreateValidator = "op_weight_msg_create_validator" //nolint:gosec
    21  	OpWeightMsgEditValidator   = "op_weight_msg_edit_validator"   //nolint:gosec
    22  	OpWeightMsgDelegate        = "op_weight_msg_delegate"         //nolint:gosec
    23  	OpWeightMsgUndelegate      = "op_weight_msg_undelegate"       //nolint:gosec
    24  	OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" //nolint:gosec
    25  )
    26  
    27  // WeightedOperations returns all the operations from the module with their respective weights
    28  func WeightedOperations(
    29  	appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper,
    30  	bk types.BankKeeper, k keeper.Keeper,
    31  ) simulation.WeightedOperations {
    32  	var (
    33  		weightMsgCreateValidator int
    34  		weightMsgEditValidator   int
    35  		weightMsgDelegate        int
    36  		weightMsgUndelegate      int
    37  		weightMsgBeginRedelegate int
    38  	)
    39  
    40  	appParams.GetOrGenerate(cdc, OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil,
    41  		func(_ *rand.Rand) {
    42  			weightMsgCreateValidator = simappparams.DefaultWeightMsgCreateValidator
    43  		},
    44  	)
    45  
    46  	appParams.GetOrGenerate(cdc, OpWeightMsgEditValidator, &weightMsgEditValidator, nil,
    47  		func(_ *rand.Rand) {
    48  			weightMsgEditValidator = simappparams.DefaultWeightMsgEditValidator
    49  		},
    50  	)
    51  
    52  	appParams.GetOrGenerate(cdc, OpWeightMsgDelegate, &weightMsgDelegate, nil,
    53  		func(_ *rand.Rand) {
    54  			weightMsgDelegate = simappparams.DefaultWeightMsgDelegate
    55  		},
    56  	)
    57  
    58  	appParams.GetOrGenerate(cdc, OpWeightMsgUndelegate, &weightMsgUndelegate, nil,
    59  		func(_ *rand.Rand) {
    60  			weightMsgUndelegate = simappparams.DefaultWeightMsgUndelegate
    61  		},
    62  	)
    63  
    64  	appParams.GetOrGenerate(cdc, OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil,
    65  		func(_ *rand.Rand) {
    66  			weightMsgBeginRedelegate = simappparams.DefaultWeightMsgBeginRedelegate
    67  		},
    68  	)
    69  
    70  	return simulation.WeightedOperations{
    71  		simulation.NewWeightedOperation(
    72  			weightMsgCreateValidator,
    73  			SimulateMsgCreateValidator(ak, bk, k),
    74  		),
    75  		simulation.NewWeightedOperation(
    76  			weightMsgEditValidator,
    77  			SimulateMsgEditValidator(ak, bk, k),
    78  		),
    79  		simulation.NewWeightedOperation(
    80  			weightMsgDelegate,
    81  			SimulateMsgDelegate(ak, bk, k),
    82  		),
    83  		simulation.NewWeightedOperation(
    84  			weightMsgUndelegate,
    85  			SimulateMsgUndelegate(ak, bk, k),
    86  		),
    87  		simulation.NewWeightedOperation(
    88  			weightMsgBeginRedelegate,
    89  			SimulateMsgBeginRedelegate(ak, bk, k),
    90  		),
    91  	}
    92  }
    93  
    94  // SimulateMsgCreateValidator generates a MsgCreateValidator with random values
    95  func SimulateMsgCreateValidator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
    96  	return func(
    97  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
    98  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
    99  		simAccount, _ := simtypes.RandomAcc(r, accs)
   100  		address := sdk.ValAddress(simAccount.Address)
   101  
   102  		// ensure the validator doesn't exist already
   103  		_, found := k.GetValidator(ctx, address)
   104  		if found {
   105  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to find validator"), nil, nil
   106  		}
   107  
   108  		denom := k.GetParams(ctx).BondDenom
   109  
   110  		balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount
   111  		if !balance.IsPositive() {
   112  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "balance is negative"), nil, nil
   113  		}
   114  
   115  		amount, err := simtypes.RandPositiveInt(r, balance)
   116  		if err != nil {
   117  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to generate positive amount"), nil, err
   118  		}
   119  
   120  		selfDelegation := sdk.NewCoin(denom, amount)
   121  
   122  		account := ak.GetAccount(ctx, simAccount.Address)
   123  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   124  
   125  		var fees sdk.Coins
   126  
   127  		coins, hasNeg := spendable.SafeSub(sdk.Coins{selfDelegation})
   128  		if !hasNeg {
   129  			fees, err = simtypes.RandomFees(r, ctx, coins)
   130  			if err != nil {
   131  				return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to generate fees"), nil, err
   132  			}
   133  		}
   134  
   135  		description := types.NewDescription(
   136  			simtypes.RandStringOfLength(r, 10),
   137  			simtypes.RandStringOfLength(r, 10),
   138  			simtypes.RandStringOfLength(r, 10),
   139  			simtypes.RandStringOfLength(r, 10),
   140  			simtypes.RandStringOfLength(r, 10),
   141  		)
   142  
   143  		maxCommission := sdk.NewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2)
   144  		commission := types.NewCommissionRates(
   145  			simtypes.RandomDecAmount(r, maxCommission),
   146  			maxCommission,
   147  			simtypes.RandomDecAmount(r, maxCommission),
   148  		)
   149  
   150  		msg, err := types.NewMsgCreateValidator(address, simAccount.ConsKey.PubKey(), selfDelegation, description, commission, sdk.OneInt())
   151  		if err != nil {
   152  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to create CreateValidator message"), nil, err
   153  		}
   154  
   155  		txCtx := simulation.OperationInput{
   156  			R:             r,
   157  			App:           app,
   158  			TxGen:         simappparams.MakeTestEncodingConfig().TxConfig,
   159  			Cdc:           nil,
   160  			Msg:           msg,
   161  			MsgType:       msg.Type(),
   162  			Context:       ctx,
   163  			SimAccount:    simAccount,
   164  			AccountKeeper: ak,
   165  			ModuleName:    types.ModuleName,
   166  		}
   167  
   168  		return simulation.GenAndDeliverTx(txCtx, fees)
   169  	}
   170  }
   171  
   172  // SimulateMsgEditValidator generates a MsgEditValidator with random values
   173  func SimulateMsgEditValidator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   174  	return func(
   175  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   176  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   177  		if len(k.GetAllValidators(ctx)) == 0 {
   178  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "number of validators equal zero"), nil, nil
   179  		}
   180  
   181  		val, ok := keeper.RandomValidator(r, k, ctx)
   182  		if !ok {
   183  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "unable to pick a validator"), nil, nil
   184  		}
   185  
   186  		address := val.GetOperator()
   187  
   188  		newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate)
   189  
   190  		if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil {
   191  			// skip as the commission is invalid
   192  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "invalid commission rate"), nil, nil
   193  		}
   194  
   195  		simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(val.GetOperator()))
   196  		if !found {
   197  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator())
   198  		}
   199  
   200  		account := ak.GetAccount(ctx, simAccount.Address)
   201  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   202  
   203  		description := types.NewDescription(
   204  			simtypes.RandStringOfLength(r, 10),
   205  			simtypes.RandStringOfLength(r, 10),
   206  			simtypes.RandStringOfLength(r, 10),
   207  			simtypes.RandStringOfLength(r, 10),
   208  			simtypes.RandStringOfLength(r, 10),
   209  		)
   210  
   211  		msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil)
   212  
   213  		txCtx := simulation.OperationInput{
   214  			R:               r,
   215  			App:             app,
   216  			TxGen:           simappparams.MakeTestEncodingConfig().TxConfig,
   217  			Cdc:             nil,
   218  			Msg:             msg,
   219  			MsgType:         msg.Type(),
   220  			Context:         ctx,
   221  			SimAccount:      simAccount,
   222  			AccountKeeper:   ak,
   223  			Bankkeeper:      bk,
   224  			ModuleName:      types.ModuleName,
   225  			CoinsSpentInMsg: spendable,
   226  		}
   227  
   228  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   229  	}
   230  }
   231  
   232  // SimulateMsgDelegate generates a MsgDelegate with random values
   233  func SimulateMsgDelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   234  	return func(
   235  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   236  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   237  		denom := k.GetParams(ctx).BondDenom
   238  
   239  		if len(k.GetAllValidators(ctx)) == 0 {
   240  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "number of validators equal zero"), nil, nil
   241  		}
   242  
   243  		simAccount, _ := simtypes.RandomAcc(r, accs)
   244  		val, ok := keeper.RandomValidator(r, k, ctx)
   245  		if !ok {
   246  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to pick a validator"), nil, nil
   247  		}
   248  
   249  		if val.InvalidExRate() {
   250  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "validator's invalid echange rate"), nil, nil
   251  		}
   252  
   253  		amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount
   254  		if !amount.IsPositive() {
   255  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "balance is negative"), nil, nil
   256  		}
   257  
   258  		amount, err := simtypes.RandPositiveInt(r, amount)
   259  		if err != nil {
   260  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to generate positive amount"), nil, err
   261  		}
   262  
   263  		bondAmt := sdk.NewCoin(denom, amount)
   264  
   265  		account := ak.GetAccount(ctx, simAccount.Address)
   266  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   267  
   268  		var fees sdk.Coins
   269  
   270  		coins, hasNeg := spendable.SafeSub(sdk.Coins{bondAmt})
   271  		if !hasNeg {
   272  			fees, err = simtypes.RandomFees(r, ctx, coins)
   273  			if err != nil {
   274  				return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to generate fees"), nil, err
   275  			}
   276  		}
   277  
   278  		msg := types.NewMsgDelegate(simAccount.Address, val.GetOperator(), bondAmt)
   279  
   280  		txCtx := simulation.OperationInput{
   281  			R:             r,
   282  			App:           app,
   283  			TxGen:         simappparams.MakeTestEncodingConfig().TxConfig,
   284  			Cdc:           nil,
   285  			Msg:           msg,
   286  			MsgType:       msg.Type(),
   287  			Context:       ctx,
   288  			SimAccount:    simAccount,
   289  			AccountKeeper: ak,
   290  			ModuleName:    types.ModuleName,
   291  		}
   292  
   293  		return simulation.GenAndDeliverTx(txCtx, fees)
   294  	}
   295  }
   296  
   297  // SimulateMsgUndelegate generates a MsgUndelegate with random values
   298  func SimulateMsgUndelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   299  	return func(
   300  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   301  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   302  		// get random validator
   303  		validator, ok := keeper.RandomValidator(r, k, ctx)
   304  		if !ok {
   305  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "validator is not ok"), nil, nil
   306  		}
   307  
   308  		valAddr := validator.GetOperator()
   309  		delegations := k.GetValidatorDelegations(ctx, validator.GetOperator())
   310  		if delegations == nil {
   311  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "keeper does have any delegation entries"), nil, nil
   312  		}
   313  
   314  		// get random delegator from validator
   315  		delegation := delegations[r.Intn(len(delegations))]
   316  		delAddr := delegation.GetDelegatorAddr()
   317  
   318  		if k.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) {
   319  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "keeper does have a max unbonding delegation entries"), nil, nil
   320  		}
   321  
   322  		totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt()
   323  		if !totalBond.IsPositive() {
   324  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "total bond is negative"), nil, nil
   325  		}
   326  
   327  		unbondAmt, err := simtypes.RandPositiveInt(r, totalBond)
   328  		if err != nil {
   329  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "invalid unbond amount"), nil, err
   330  		}
   331  
   332  		if unbondAmt.IsZero() {
   333  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "unbond amount is zero"), nil, nil
   334  		}
   335  
   336  		msg := types.NewMsgUndelegate(
   337  			delAddr, valAddr, sdk.NewCoin(k.BondDenom(ctx), unbondAmt),
   338  		)
   339  
   340  		// need to retrieve the simulation account associated with delegation to retrieve PrivKey
   341  		var simAccount simtypes.Account
   342  
   343  		for _, simAcc := range accs {
   344  			if simAcc.Address.Equals(delAddr) {
   345  				simAccount = simAcc
   346  				break
   347  			}
   348  		}
   349  		// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
   350  		if simAccount.PrivKey == nil {
   351  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr)
   352  		}
   353  
   354  		account := ak.GetAccount(ctx, delAddr)
   355  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   356  
   357  		txCtx := simulation.OperationInput{
   358  			R:               r,
   359  			App:             app,
   360  			TxGen:           simappparams.MakeTestEncodingConfig().TxConfig,
   361  			Cdc:             nil,
   362  			Msg:             msg,
   363  			MsgType:         msg.Type(),
   364  			Context:         ctx,
   365  			SimAccount:      simAccount,
   366  			AccountKeeper:   ak,
   367  			Bankkeeper:      bk,
   368  			ModuleName:      types.ModuleName,
   369  			CoinsSpentInMsg: spendable,
   370  		}
   371  
   372  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   373  	}
   374  }
   375  
   376  // SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
   377  func SimulateMsgBeginRedelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   378  	return func(
   379  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   380  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   381  		// get random source validator
   382  		srcVal, ok := keeper.RandomValidator(r, k, ctx)
   383  		if !ok {
   384  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to pick validator"), nil, nil
   385  		}
   386  
   387  		srcAddr := srcVal.GetOperator()
   388  		delegations := k.GetValidatorDelegations(ctx, srcAddr)
   389  		if delegations == nil {
   390  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "keeper does have any delegation entries"), nil, nil
   391  		}
   392  
   393  		// get random delegator from src validator
   394  		delegation := delegations[r.Intn(len(delegations))]
   395  		delAddr := delegation.GetDelegatorAddr()
   396  
   397  		if k.HasReceivingRedelegation(ctx, delAddr, srcAddr) {
   398  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "receveing redelegation is not allowed"), nil, nil // skip
   399  		}
   400  
   401  		// get random destination validator
   402  		destVal, ok := keeper.RandomValidator(r, k, ctx)
   403  		if !ok {
   404  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to pick validator"), nil, nil
   405  		}
   406  
   407  		destAddr := destVal.GetOperator()
   408  		if srcAddr.Equals(destAddr) || destVal.InvalidExRate() || k.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) {
   409  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "checks failed"), nil, nil
   410  		}
   411  
   412  		totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
   413  		if !totalBond.IsPositive() {
   414  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "total bond is negative"), nil, nil
   415  		}
   416  
   417  		redAmt, err := simtypes.RandPositiveInt(r, totalBond)
   418  		if err != nil {
   419  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to generate positive amount"), nil, err
   420  		}
   421  
   422  		if redAmt.IsZero() {
   423  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "amount is zero"), nil, nil
   424  		}
   425  
   426  		// check if the shares truncate to zero
   427  		shares, err := srcVal.SharesFromTokens(redAmt)
   428  		if err != nil {
   429  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "invalid shares"), nil, err
   430  		}
   431  
   432  		if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
   433  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "shares truncate to zero"), nil, nil // skip
   434  		}
   435  
   436  		// need to retrieve the simulation account associated with delegation to retrieve PrivKey
   437  		var simAccount simtypes.Account
   438  
   439  		for _, simAcc := range accs {
   440  			if simAcc.Address.Equals(delAddr) {
   441  				simAccount = simAcc
   442  				break
   443  			}
   444  		}
   445  
   446  		// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
   447  		if simAccount.PrivKey == nil {
   448  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr)
   449  		}
   450  
   451  		account := ak.GetAccount(ctx, delAddr)
   452  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   453  
   454  		msg := types.NewMsgBeginRedelegate(
   455  			delAddr, srcAddr, destAddr,
   456  			sdk.NewCoin(k.BondDenom(ctx), redAmt),
   457  		)
   458  
   459  		txCtx := simulation.OperationInput{
   460  			R:               r,
   461  			App:             app,
   462  			TxGen:           simappparams.MakeTestEncodingConfig().TxConfig,
   463  			Cdc:             nil,
   464  			Msg:             msg,
   465  			MsgType:         msg.Type(),
   466  			Context:         ctx,
   467  			SimAccount:      simAccount,
   468  			AccountKeeper:   ak,
   469  			Bankkeeper:      bk,
   470  			ModuleName:      types.ModuleName,
   471  			CoinsSpentInMsg: spendable,
   472  		}
   473  
   474  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   475  	}
   476  }