package simulation import ( "math/rand" "time" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp/helpers" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/simulation" appparams "github.com/kava-labs/kava/app/params" "github.com/kava-labs/kava/x/pricefeed/keeper" "github.com/kava-labs/kava/x/pricefeed/types" ) // Simulation operation weights constants const ( OpWeightMsgUpdatePrices = "op_weight_msg_update_prices" ) // WeightedOperations returns all the operations from the module with their respective weights func WeightedOperations( appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, k keeper.Keeper, ) simulation.WeightedOperations { var weightMsgUpdatePrices int // var numBlocks int appParams.GetOrGenerate(cdc, OpWeightMsgUpdatePrices, &weightMsgUpdatePrices, nil, func(_ *rand.Rand) { weightMsgUpdatePrices = appparams.DefaultWeightMsgUpdatePrices }, ) return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgUpdatePrices, SimulateMsgUpdatePrices(ak, k, 10000), ), } } // SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price func SimulateMsgUpdatePrices(ak auth.AccountKeeper, keeper keeper.Keeper, blocks int) simulation.Operation { // runs one at the start of each simulation startingPrices := map[string]sdk.Dec{ "btc:usd": sdk.MustNewDecFromStr("7000"), "bnb:usd": sdk.MustNewDecFromStr("15"), "xrp:usd": sdk.MustNewDecFromStr("0.25"), } // creates the new price generator from starting prices - resets for each sim priceGenerator := NewPriceGenerator(startingPrices) return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, ) (simulation.OperationMsg, []simulation.FutureOperation, error) { // walk prices to current block height, noop if already called for current height priceGenerator.Step(r, ctx.BlockHeight()) randomMarket := pickRandomAsset(ctx, keeper, r) marketID := randomMarket.MarketID address := getRandomOracle(r, randomMarket) oracle, found := simulation.FindAccount(accs, address) if !found { return simulation.NoOpMsg(types.ModuleName), nil, nil } oracleAcc := ak.GetAccount(ctx, oracle.Address) if oracleAcc == nil { return simulation.NoOpMsg(types.ModuleName), nil, nil } // get price for marketID and current block height set in Step price := priceGenerator.GetCurrentPrice(marketID) // get the expiry time based off the current time expiry := getExpiryTime(ctx) // now create the msg to post price msg := types.NewMsgPostPrice(oracle.Address, marketID, price, expiry) spendable := oracleAcc.SpendableCoins(ctx.BlockTime()) fees, err := simulation.RandomFees(r, ctx, spendable) if err != nil { return simulation.NoOpMsg(types.ModuleName), nil, err } tx := helpers.GenTx( []sdk.Msg{msg}, fees, helpers.DefaultGenTxGas, chainID, []uint64{oracleAcc.GetAccountNumber()}, []uint64{oracleAcc.GetSequence()}, oracle.PrivKey, ) _, result, err := app.Deliver(tx) if err != nil { return simulation.NoOpMsg(types.ModuleName), nil, err } return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } } // getRandomOracle picks a random oracle from the list of oracles func getRandomOracle(r *rand.Rand, market types.Market) sdk.AccAddress { randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles)) return market.Oracles[randomIndex] } // pickRandomAsset picks a random asset out of the assets with equal probability // it returns the Market which includes the base asset as one of its fields func pickRandomAsset(ctx sdk.Context, keeper keeper.Keeper, r *rand.Rand) (market types.Market) { // get the params params := keeper.GetParams(ctx) // now pick a random asset randomIndex := simulation.RandIntBetween(r, 0, len(params.Markets)) return params.Markets[randomIndex] } // getExpiryTime gets a price expiry time by taking the current time and adding a delta to it func getExpiryTime(ctx sdk.Context) (t time.Time) { // need to use the blocktime from the context as the context generates random start time when running simulations return ctx.BlockTime().Add(time.Second * 1000000) }