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 }