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