github.com/gravity-devs/liquidity@v1.5.3/x/liquidity/simulation/operations.go (about)

     1  package simulation
     2  
     3  import (
     4  	"math/rand"
     5  
     6  	"github.com/cosmos/cosmos-sdk/baseapp"
     7  	"github.com/cosmos/cosmos-sdk/codec"
     8  	"github.com/cosmos/cosmos-sdk/simapp/helpers"
     9  	sdk "github.com/cosmos/cosmos-sdk/types"
    10  	simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
    11  	"github.com/cosmos/cosmos-sdk/x/simulation"
    12  
    13  	liquidityparams "github.com/gravity-devs/liquidity/app/params"
    14  	"github.com/gravity-devs/liquidity/x/liquidity/keeper"
    15  	"github.com/gravity-devs/liquidity/x/liquidity/types"
    16  )
    17  
    18  // Simulation operation weights constants.
    19  //
    20  //nolint:gosec
    21  const (
    22  	OpWeightMsgCreatePool          = "op_weight_msg_create_pool"
    23  	OpWeightMsgDepositWithinBatch  = "op_weight_msg_deposit_to_pool"
    24  	OpWeightMsgWithdrawWithinBatch = "op_weight_msg_withdraw_from_pool"
    25  	OpWeightMsgSwapWithinBatch     = "op_weight_msg_swap"
    26  )
    27  
    28  // WeightedOperations returns all the operations from the module with their respective weights.
    29  func WeightedOperations(
    30  	appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper,
    31  	bk types.BankKeeper, k keeper.Keeper,
    32  ) simulation.WeightedOperations {
    33  	var weightMsgCreatePool int
    34  	appParams.GetOrGenerate(cdc, OpWeightMsgCreatePool, &weightMsgCreatePool, nil,
    35  		func(_ *rand.Rand) {
    36  			weightMsgCreatePool = liquidityparams.DefaultWeightMsgCreatePool
    37  		},
    38  	)
    39  
    40  	var weightMsgDepositWithinBatch int
    41  	appParams.GetOrGenerate(cdc, OpWeightMsgDepositWithinBatch, &weightMsgDepositWithinBatch, nil,
    42  		func(_ *rand.Rand) {
    43  			weightMsgDepositWithinBatch = liquidityparams.DefaultWeightMsgDepositWithinBatch
    44  		},
    45  	)
    46  
    47  	var weightMsgMsgWithdrawWithinBatch int
    48  	appParams.GetOrGenerate(cdc, OpWeightMsgWithdrawWithinBatch, &weightMsgMsgWithdrawWithinBatch, nil,
    49  		func(_ *rand.Rand) {
    50  			weightMsgMsgWithdrawWithinBatch = liquidityparams.DefaultWeightMsgWithdrawWithinBatch
    51  		},
    52  	)
    53  
    54  	var weightMsgSwapWithinBatch int
    55  	appParams.GetOrGenerate(cdc, OpWeightMsgSwapWithinBatch, &weightMsgSwapWithinBatch, nil,
    56  		func(_ *rand.Rand) {
    57  			weightMsgSwapWithinBatch = liquidityparams.DefaultWeightMsgSwapWithinBatch
    58  		},
    59  	)
    60  
    61  	return simulation.WeightedOperations{
    62  		simulation.NewWeightedOperation(
    63  			weightMsgCreatePool,
    64  			SimulateMsgCreatePool(ak, bk, k),
    65  		),
    66  		simulation.NewWeightedOperation(
    67  			weightMsgDepositWithinBatch,
    68  			SimulateMsgDepositWithinBatch(ak, bk, k),
    69  		),
    70  		simulation.NewWeightedOperation(
    71  			weightMsgMsgWithdrawWithinBatch,
    72  			SimulateMsgWithdrawWithinBatch(ak, bk, k),
    73  		),
    74  		simulation.NewWeightedOperation(
    75  			weightMsgSwapWithinBatch,
    76  			SimulateMsgSwapWithinBatch(ak, bk, k),
    77  		),
    78  	}
    79  }
    80  
    81  // SimulateMsgCreatePool generates a MsgCreatePool with random values
    82  func SimulateMsgCreatePool(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
    83  	return func(
    84  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
    85  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
    86  		var simAccount simtypes.Account
    87  		randomAccounts = simtypes.RandomAccounts(r, 3)
    88  		simAccount = randomAccounts[r.Intn(3)]
    89  
    90  		params := k.GetParams(ctx)
    91  		params.MaxReserveCoinAmount = GenMaxReserveCoinAmount(r)
    92  		k.SetParams(ctx, params)
    93  
    94  		// get randomized two denoms to create liquidity pool
    95  		var mintingDenoms []string
    96  		denomA, denomB := randomDenoms(r)
    97  		reserveCoinDenoms := []string{denomA, denomB}
    98  		mintingDenoms = append(mintingDenoms, reserveCoinDenoms...)
    99  
   100  		// simAccount should have some fees to pay for transaction and pool creation fee
   101  		var feeDenoms []string
   102  		for _, fee := range params.PoolCreationFee {
   103  			feeDenoms = append(feeDenoms, fee.GetDenom())
   104  		}
   105  		mintingDenoms = append(mintingDenoms, feeDenoms...)
   106  
   107  		// mint coins of randomized and fee denoms
   108  		err := mintCoins(ctx, r, bk, simAccount, mintingDenoms)
   109  		if err != nil {
   110  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "unable to mint and send coins"), nil, err
   111  		}
   112  
   113  		account := ak.GetAccount(ctx, simAccount.Address)
   114  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   115  		poolName := types.PoolName(reserveCoinDenoms, types.DefaultPoolTypeID)
   116  		reserveAcc := types.GetPoolReserveAcc(poolName, false)
   117  
   118  		// ensure the liquidity pool doesn't exist
   119  		_, found := k.GetPoolByReserveAccIndex(ctx, reserveAcc)
   120  		if found {
   121  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "liquidity pool already exists"), nil, nil
   122  		}
   123  
   124  		balanceA := bk.GetBalance(ctx, simAccount.Address, denomA).Amount
   125  		if balanceA.IsNegative() {
   126  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "balanceA is negative"), nil, nil
   127  		}
   128  
   129  		balanceB := bk.GetBalance(ctx, simAccount.Address, denomB).Amount
   130  		if balanceB.IsNegative() {
   131  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "balanceB is negative"), nil, nil
   132  		}
   133  
   134  		poolCreator := account.GetAddress()
   135  		depositCoinA := randomDepositCoin(r, params.MinInitDepositAmount, denomA)
   136  		depositCoinB := randomDepositCoin(r, params.MinInitDepositAmount, denomB)
   137  		depositCoins := sdk.NewCoins(depositCoinA, depositCoinB)
   138  
   139  		// it will fail if the total reserve coin amount after the deposit is larger than the parameter
   140  		err = types.ValidateReserveCoinLimit(params.MaxReserveCoinAmount, depositCoins)
   141  		if err != nil {
   142  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "can not exceed reserve coin limit amount"), nil, nil
   143  		}
   144  
   145  		msg := types.NewMsgCreatePool(poolCreator, types.DefaultPoolTypeID, depositCoins)
   146  
   147  		fees, err := randomFees(r, spendable)
   148  		if err != nil {
   149  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreatePool, "unable to generate fees"), nil, err
   150  		}
   151  
   152  		txGen := liquidityparams.MakeTestEncodingConfig().TxConfig
   153  		tx, err := helpers.GenTx(
   154  			txGen,
   155  			[]sdk.Msg{msg},
   156  			fees,
   157  			helpers.DefaultGenTxGas,
   158  			chainID,
   159  			[]uint64{account.GetAccountNumber()},
   160  			[]uint64{account.GetSequence()},
   161  			simAccount.PrivKey,
   162  		)
   163  		if err != nil {
   164  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
   165  		}
   166  
   167  		_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   168  		if err != nil {
   169  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err
   170  		}
   171  
   172  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   173  	}
   174  }
   175  
   176  // SimulateMsgDepositWithinBatch  generates a MsgDepositWithinBatch  with random values
   177  func SimulateMsgDepositWithinBatch(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   178  	return func(
   179  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   180  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   181  		if len(k.GetAllPools(ctx)) == 0 {
   182  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositWithinBatch, "number of liquidity pools equals zero"), nil, nil
   183  		}
   184  
   185  		pool, ok := randomLiquidity(r, k, ctx)
   186  		if !ok {
   187  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositWithinBatch, "unable to pick liquidity pool"), nil, nil
   188  		}
   189  
   190  		reserveCoinDenomA := pool.ReserveCoinDenoms[0]
   191  		reserveCoinDenomB := pool.ReserveCoinDenoms[1]
   192  
   193  		// select random simulated account and mint reserve coins
   194  		// note that select the simulated account that has some balances of reserve coin denoms result in
   195  		// many failed transactions due to random accounts change after a creating pool.
   196  		simAccount := randomAccounts[r.Intn(len(randomAccounts))]
   197  		err := mintCoins(ctx, r, bk, simAccount, []string{reserveCoinDenomA, reserveCoinDenomB})
   198  		if err != nil {
   199  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositWithinBatch, "unable to mint and send coins"), nil, err
   200  		}
   201  
   202  		params := k.GetParams(ctx)
   203  		params.MaxReserveCoinAmount = GenMaxReserveCoinAmount(r)
   204  		k.SetParams(ctx, params)
   205  
   206  		account := ak.GetAccount(ctx, simAccount.Address)
   207  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   208  		depositor := account.GetAddress()
   209  		depositCoinA := randomDepositCoin(r, params.MinInitDepositAmount, reserveCoinDenomA)
   210  		depositCoinB := randomDepositCoin(r, params.MinInitDepositAmount, reserveCoinDenomB)
   211  		depositCoins := sdk.NewCoins(depositCoinA, depositCoinB)
   212  
   213  		reserveCoins := k.GetReserveCoins(ctx, pool)
   214  
   215  		// it will fail if the total reserve coin amount after the deposit is larger than the parameter
   216  		err = types.ValidateReserveCoinLimit(params.MaxReserveCoinAmount, reserveCoins.Add(depositCoinA, depositCoinB))
   217  		if err != nil {
   218  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositWithinBatch, "can not exceed reserve coin limit amount"), nil, nil
   219  		}
   220  
   221  		fees, err := randomFees(r, spendable)
   222  		if err != nil {
   223  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositWithinBatch, "unable to generate fees"), nil, err
   224  		}
   225  
   226  		msg := types.NewMsgDepositWithinBatch(depositor, pool.Id, depositCoins)
   227  
   228  		txGen := liquidityparams.MakeTestEncodingConfig().TxConfig
   229  		tx, err := helpers.GenTx(
   230  			txGen,
   231  			[]sdk.Msg{msg},
   232  			fees,
   233  			helpers.DefaultGenTxGas,
   234  			chainID,
   235  			[]uint64{account.GetAccountNumber()},
   236  			[]uint64{account.GetSequence()},
   237  			simAccount.PrivKey,
   238  		)
   239  		if err != nil {
   240  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
   241  		}
   242  
   243  		_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   244  		if err != nil {
   245  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err
   246  		}
   247  
   248  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   249  	}
   250  }
   251  
   252  // SimulateMsgWithdrawWithinBatch generates a MsgWithdrawWithinBatch with random values.
   253  func SimulateMsgWithdrawWithinBatch(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   254  	return func(
   255  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   256  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   257  		if len(k.GetAllPools(ctx)) == 0 {
   258  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWithdrawWithinBatch, "number of liquidity pools equals zero"), nil, nil
   259  		}
   260  
   261  		pool, ok := randomLiquidity(r, k, ctx)
   262  		if !ok {
   263  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWithdrawWithinBatch, "unable to pick liquidity pool"), nil, nil
   264  		}
   265  
   266  		poolCoinDenom := pool.GetPoolCoinDenom()
   267  
   268  		// select random simulated account and mint reserve coins
   269  		// note that select the simulated account that has some balance of pool coin denom result in
   270  		// many failed transactions due to random accounts change after a creating pool.
   271  		simAccount := randomAccounts[r.Intn(len(randomAccounts))]
   272  		err := mintCoins(ctx, r, bk, simAccount, []string{poolCoinDenom})
   273  		if err != nil {
   274  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWithdrawWithinBatch, "unable to mint and send coins"), nil, err
   275  		}
   276  
   277  		// if simAccount.PrivKey == nil, then no account has pool coin denom balanace
   278  		if simAccount.PrivKey == nil {
   279  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWithdrawWithinBatch, "account private key is nil"), nil, nil
   280  		}
   281  
   282  		account := ak.GetAccount(ctx, simAccount.Address)
   283  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   284  		balance := bk.GetBalance(ctx, simAccount.Address, poolCoinDenom)
   285  		withdrawer := account.GetAddress()
   286  		withdrawCoin := randomWithdrawCoin(r, poolCoinDenom, balance.Amount)
   287  
   288  		msg := types.NewMsgWithdrawWithinBatch(withdrawer, pool.Id, withdrawCoin)
   289  
   290  		fees, err := randomFees(r, spendable)
   291  		if err != nil {
   292  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWithdrawWithinBatch, "unable to generate fees"), nil, err
   293  		}
   294  
   295  		txGen := liquidityparams.MakeTestEncodingConfig().TxConfig
   296  		tx, err := helpers.GenTx(
   297  			txGen,
   298  			[]sdk.Msg{msg},
   299  			fees,
   300  			helpers.DefaultGenTxGas,
   301  			chainID,
   302  			[]uint64{account.GetAccountNumber()},
   303  			[]uint64{account.GetSequence()},
   304  			simAccount.PrivKey,
   305  		)
   306  		if err != nil {
   307  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
   308  		}
   309  
   310  		_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   311  		if err != nil {
   312  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err
   313  		}
   314  
   315  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   316  	}
   317  }
   318  
   319  // SimulateMsgSwapWithinBatch generates a MsgSwapWithinBatch with random values
   320  func SimulateMsgSwapWithinBatch(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation {
   321  	return func(
   322  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   323  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   324  		if len(k.GetAllPools(ctx)) == 0 {
   325  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapWithinBatch, "number of liquidity pools equals zero"), nil, nil
   326  		}
   327  
   328  		pool, ok := randomLiquidity(r, k, ctx)
   329  		if !ok {
   330  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapWithinBatch, "unable to pick liquidity pool"), nil, nil
   331  		}
   332  
   333  		reserveCoinDenomA := pool.ReserveCoinDenoms[0]
   334  		reserveCoinDenomB := pool.ReserveCoinDenoms[1]
   335  
   336  		// select random simulated account and mint reserve coins
   337  		// note that select the simulated account that has some balances of reserve coin denoms result in
   338  		// many failed transactions due to random accounts change after a creating pool.
   339  		simAccount := randomAccounts[r.Intn(len(randomAccounts))]
   340  		err := mintCoins(ctx, r, bk, simAccount, []string{reserveCoinDenomA, reserveCoinDenomB})
   341  		if err != nil {
   342  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapWithinBatch, "unable to mint and send coins"), nil, err
   343  		}
   344  
   345  		account := ak.GetAccount(ctx, simAccount.Address)
   346  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   347  		swapRequester := account.GetAddress()
   348  		offerCoin := randomOfferCoin(r, k, ctx, pool, pool.ReserveCoinDenoms[0])
   349  		demandCoinDenom := pool.ReserveCoinDenoms[1]
   350  		orderPrice := randomOrderPrice(r)
   351  		swapFeeRate := GenSwapFeeRate(r)
   352  
   353  		// set randomly generated swap fee rate in params to prevent from miscalculation
   354  		params := k.GetParams(ctx)
   355  		params.SwapFeeRate = swapFeeRate
   356  		k.SetParams(ctx, params)
   357  
   358  		msg := types.NewMsgSwapWithinBatch(swapRequester, pool.Id, types.DefaultSwapTypeID, offerCoin, demandCoinDenom, orderPrice, swapFeeRate)
   359  
   360  		fees, err := randomFees(r, spendable)
   361  		if err != nil {
   362  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapWithinBatch, "unable to generate fees"), nil, err
   363  		}
   364  
   365  		txGen := liquidityparams.MakeTestEncodingConfig().TxConfig
   366  		tx, err := helpers.GenTx(
   367  			txGen,
   368  			[]sdk.Msg{msg},
   369  			fees,
   370  			helpers.DefaultGenTxGas,
   371  			chainID,
   372  			[]uint64{account.GetAccountNumber()},
   373  			[]uint64{account.GetSequence()},
   374  			simAccount.PrivKey,
   375  		)
   376  		if err != nil {
   377  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err
   378  		}
   379  
   380  		_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   381  		if err != nil {
   382  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err
   383  		}
   384  
   385  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   386  	}
   387  }