diff --git a/app/sim_test.go b/app/sim_test.go index ede0aaf1..5a3b1b00 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -34,6 +34,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" "github.com/cosmos/cosmos-sdk/x/supply" + + bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" + pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" ) // Simulation parameter constants @@ -56,6 +59,8 @@ const ( OpWeightMsgUndelegate = "op_weight_msg_undelegate" OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" OpWeightMsgUnjail = "op_weight_msg_unjail" + OpWeightMsgPricefeed = "op_weight_msg_pricefeed" + OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" ) // TestMain runs setup and teardown code before all tests. @@ -264,6 +269,28 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }(nil), slashingsimops.SimulateMsgUnjail(app.slashingKeeper), }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgCreateAtomicSwap, &v, nil, + func(_ *rand.Rand) { + v = 100 + }) + return v + }(nil), + bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper), + }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, + func(_ *rand.Rand) { + v = 10000 // TODO + }) + return v + }(nil), + pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper), + }, } } diff --git a/x/bep3/simulation/decoder.go b/x/bep3/simulation/decoder.go index b5ff6001..351b0b00 100644 --- a/x/bep3/simulation/decoder.go +++ b/x/bep3/simulation/decoder.go @@ -14,7 +14,7 @@ import ( func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { switch { case bytes.Equal(kvA.Key[:1], types.AtomicSwapKeyPrefix): - var swapA, swapB *types.AtomicSwap + var swapA, swapB types.AtomicSwap cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &swapA) cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &swapB) return fmt.Sprintf("%v\n%v", swapA, swapB) diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go index 5daaab0e..b5402736 100644 --- a/x/bep3/simulation/genesis.go +++ b/x/bep3/simulation/genesis.go @@ -2,21 +2,167 @@ package simulation import ( "fmt" + "math/rand" + "strings" + "time" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/kava-labs/kava/x/bep3/types" ) +// Simulation parameter constants +const ( + BnbDeputyAddress = "bnb_deputy_address" + MinBlockLock = "min_block_lock" + MaxBlockLock = "max_block_lock" + SupportedAssets = "supported_assets" +) + +var ( + MaxSupplyLimit = sdk.NewInt(10000000000000000) + Accs []simulation.Account + ConsistentDenoms = [3]string{"bnb", "xrp", "btc"} +) + +// GenBnbDeputyAddress randomized BnbDeputyAddress +func GenBnbDeputyAddress(r *rand.Rand) sdk.AccAddress { + return simulation.RandomAcc(r, Accs).Address +} + +// GenMinBlockLock randomized MinBlockLock +func GenMinBlockLock(r *rand.Rand) int64 { + min := int(types.AbsoluteMinimumBlockLock) + max := int(types.AbsoluteMaximumBlockLock) + return int64(r.Intn(max-min) + min) +} + +// GenMaxBlockLock randomized MaxBlockLock +func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 { + min := int(minBlockLock) + max := int(types.AbsoluteMaximumBlockLock) + return int64(r.Intn(max-min) + min) +} + +// GenSupportedAssets gets randomized SupportedAssets +func GenSupportedAssets(r *rand.Rand) types.AssetParams { + var assets types.AssetParams + for i := 0; i < (r.Intn(10) + 1); i++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) + asset := genSupportedAsset(r, denom) + assets = append(assets, asset) + } + // Add bnb, btc, or xrp as a supported asset for interactions with other modules + stableAsset := genSupportedAsset(r, ConsistentDenoms[r.Intn(3)]) + assets = append(assets, stableAsset) + + return assets +} + +func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam { + coinID, _ := simulation.RandPositiveInt(r, sdk.NewInt(100000)) + limit, _ := simulation.RandPositiveInt(r, MaxSupplyLimit) + return types.AssetParam{ + Denom: denom, + CoinID: int(coinID.Int64()), + Limit: limit, + Active: true, + } +} + // RandomizedGenState generates a random GenesisState func RandomizedGenState(simState *module.SimulationState) { + Accs = simState.Accounts - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - genesisState := types.DefaultGenesisState() + bep3Genesis := loadRandomBep3GenState(simState) + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bep3Genesis) - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) + authGenesis, totalCoins := loadAuthGenState(simState, bep3Genesis) + simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) + + // Update supply to match amount of coins in auth + var supplyGenesis supply.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) + for _, deputyCoin := range totalCoins { + supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin) + } + simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) +} + +func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState { + bnbDeputyAddress := simulation.RandomAcc(simState.Rand, simState.Accounts).Address + + // min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100 + minBlockLock := int64(types.AbsoluteMinimumBlockLock) + maxBlockLock := minBlockLock * 2 + + var supportedAssets types.AssetParams + simState.AppParams.GetOrGenerate( + simState.Cdc, SupportedAssets, &supportedAssets, simState.Rand, + func(r *rand.Rand) { supportedAssets = GenSupportedAssets(r) }, + ) + + bep3Genesis := types.GenesisState{ + Params: types.Params{ + BnbDeputyAddress: bnbDeputyAddress, + MinBlockLock: minBlockLock, + MaxBlockLock: maxBlockLock, + SupportedAssets: supportedAssets, + }, + } + + return bep3Genesis +} + +func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.GenesisState) ( + auth.GenesisState, []sdk.Coins) { + var authGenesis auth.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) + + deputy, found := getAccount(authGenesis.Accounts, bep3Genesis.Params.BnbDeputyAddress) + if !found { + panic("deputy address not found in available accounts") + } + + // Load total limit of each supported asset to deputy's account + var totalCoins []sdk.Coins + for _, asset := range bep3Genesis.Params.SupportedAssets { + assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit)) + if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin)); err != nil { + panic(err) + } + totalCoins = append(totalCoins, assetCoin) + } + authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, deputy) + + return authGenesis, totalCoins +} + +// Return an account from a list of accounts that matches an address. +func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) { + for _, a := range accounts { + if a.GetAddress().Equals(addr) { + return a, true + } + } + return nil, false +} + +// In a list of accounts, replace the first account found with the same address. If not found, append the account. +func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount { + newAccounts := accounts + for i, a := range accounts { + if a.GetAddress().Equals(acc.GetAddress()) { + newAccounts[i] = acc + return newAccounts + } + } + return append(newAccounts, acc) } diff --git a/x/bep3/simulation/operations/msg.go b/x/bep3/simulation/operations/msg.go new file mode 100644 index 00000000..2f0fa6a0 --- /dev/null +++ b/x/bep3/simulation/operations/msg.go @@ -0,0 +1,153 @@ +package operations + +import ( + "fmt" + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/bep3" + "github.com/kava-labs/kava/x/bep3/keeper" + "github.com/kava-labs/kava/x/bep3/types" +) + +var ( + noOpMsg = simulation.NoOpMsg(bep3.ModuleName) +) + +// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values +func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation { + handler := bep3.NewHandler(k) + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + sender := k.GetBnbDeputyAddress(ctx) + recipient := simulation.RandomAcc(r, accs).Address + + recipientOtherChain := simulation.RandStringOfLength(r, 43) + senderOtherChain := simulation.RandStringOfLength(r, 43) + + // Generate cryptographically strong pseudo-random number + randomNumber, err := types.GenerateSecureRandomNumber() + if err != nil { + return noOpMsg, nil, err + } + // Must use current blocktime instead of 'now' since initial blocktime was randomly generated + timestamp := ctx.BlockTime().Unix() + randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) + + // Randomly select an asset from supported assets + assets, found := k.GetAssets(ctx) + if !found { + return noOpMsg, nil, fmt.Errorf("no supported assets found") + } + asset := assets[r.Intn(len(assets))] + + // Check that the sender has coins of this type + availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom) + if !availableAmount.IsPositive() { + return noOpMsg, nil, fmt.Errorf("available amount must be positive") + } + + // Get a random amount of the available coins + amount, err := simulation.RandPositiveInt(r, availableAmount) + if err != nil { + return noOpMsg, nil, err + } + + // If we don't adjust the conversion factor, we'll be out of funds soon + adjustedAmount := amount.Int64() / int64(math.Pow10(8)) + coin := sdk.NewInt64Coin(asset.Denom, adjustedAmount) + coins := sdk.NewCoins(coin) + expectedIncome := coin.String() + + // We're assuming that sims are run with -NumBlocks=100 + heightSpan := int64(55) + crossChain := true + + msg := types.NewMsgCreateAtomicSwap( + sender, recipient, recipientOtherChain, senderOtherChain, randomNumberHash, + timestamp, coins, expectedIncome, heightSpan, crossChain) + + if err := msg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgCreateAtomicSwap to pass ValidateBasic: %s", err) + } + + // Submit msg + ok := submitMsg(ctx, handler, msg) + + // If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation + var futureOp simulation.FutureOperation + if ok { + swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain) + acc := simulation.RandomAcc(r, accs) + evenOdd := r.Intn(2) + 1 + if evenOdd%2 == 0 { + // Claim future operation + executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) + futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler) + } else { + // Refund future operation + executionBlock := ctx.BlockHeight() + msg.HeightSpan + futureOp = loadRefundFutureOp(acc.Address, swapID, executionBlock, handler) + } + } + + return simulation.NewOperationMsg(msg, ok, ""), []simulation.FutureOperation{futureOp}, nil + } +} + +func loadClaimFutureOp(sender sdk.AccAddress, swapID []byte, randomNumber []byte, height int64, handler sdk.Handler) simulation.FutureOperation { + claimOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + // Build the refund msg and validate basic + claimMsg := types.NewMsgClaimAtomicSwap(sender, swapID, randomNumber) + if err := claimMsg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgClaimAtomicSwap to pass ValidateBasic: %s", err) + } + + // Test msg submission at target block height + ok := handler(ctx.WithBlockHeight(height), claimMsg).IsOK() + return simulation.NewOperationMsg(claimMsg, ok, ""), nil, nil + } + + return simulation.FutureOperation{ + BlockHeight: int(height), + Op: claimOp, + } +} + +func loadRefundFutureOp(sender sdk.AccAddress, swapID []byte, height int64, handler sdk.Handler) simulation.FutureOperation { + refundOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + // Build the refund msg and validate basic + refundMsg := types.NewMsgRefundAtomicSwap(sender, swapID) + if err := refundMsg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgRefundAtomicSwap to pass ValidateBasic: %s", err) + } + + // Test msg submission at target block height + ok := handler(ctx.WithBlockHeight(height), refundMsg).IsOK() + return simulation.NewOperationMsg(refundMsg, ok, ""), nil, nil + } + + return simulation.FutureOperation{ + BlockHeight: int(height), + Op: refundOp, + } +} + +func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { + ctx, write := ctx.CacheContext() + ok = handler(ctx, msg).IsOK() + if ok { + write() + } + return ok +} diff --git a/x/bep3/simulation/params.go b/x/bep3/simulation/params.go index 8c1f7aff..41c3a346 100644 --- a/x/bep3/simulation/params.go +++ b/x/bep3/simulation/params.go @@ -1,14 +1,46 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/bep3/types" +) + +const ( + keyBnbDeputyAddress = "BnbDeputyAddress" + keyMinBlockLock = "MinBlockLock" + keyMaxBlockLock = "MaxBlockLock" + keySupportedAssets = "SupportedAssets" ) // ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation func ParamChanges(r *rand.Rand) []simulation.ParamChange { - // TODO implement this - return []simulation.ParamChange{} + // We generate MinBlockLock first because the result is required by GenMaxBlockLock() + minBlockLockVal := GenMinBlockLock(r) + + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", GenBnbDeputyAddress(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", minBlockLockVal) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%v\"", GenSupportedAssets(r)) + }, + ), + } } diff --git a/x/cdp/simulation/decoder.go b/x/cdp/simulation/decoder.go index 202838a8..b2bc1fd0 100644 --- a/x/cdp/simulation/decoder.go +++ b/x/cdp/simulation/decoder.go @@ -22,6 +22,12 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &cdpIDsB) return fmt.Sprintf("%v\n%v", cdpIDsA, cdpIDsB) + case bytes.Equal(kvA.Key[:1], types.CdpKeyPrefix): + var cdpA, cdpB types.CDP + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &cdpA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &cdpB) + return fmt.Sprintf("%v\n%v", cdpA, cdpB) + case bytes.Equal(kvA.Key[:1], types.CdpIDKey), bytes.Equal(kvA.Key[:1], types.CollateralRatioIndexPrefix): idA := binary.BigEndian.Uint64(kvA.Value) diff --git a/x/cdp/simulation/decoder_test.go b/x/cdp/simulation/decoder_test.go index 5790ea6d..52927624 100644 --- a/x/cdp/simulation/decoder_test.go +++ b/x/cdp/simulation/decoder_test.go @@ -28,12 +28,15 @@ func TestDecodeDistributionStore(t *testing.T) { cdpIds := []uint64{1, 2, 3, 4, 5} denom := "denom" - deposit := types.Deposit{CdpID: 1, Amount: sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt()))} + oneCoins := sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt())) + deposit := types.Deposit{CdpID: 1, Amount: oneCoins} principal := sdk.OneInt() prevDistTime := time.Now().UTC() + cdp := types.CDP{ID: 1, FeesUpdated: prevDistTime, Collateral: oneCoins, Principal: oneCoins, AccumulatedFees: oneCoins} kvPairs := cmn.KVPairs{ cmn.KVPair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)}, + cmn.KVPair{Key: types.CdpKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdp)}, cmn.KVPair{Key: types.CdpIDKey, Value: sdk.Uint64ToBigEndian(2)}, cmn.KVPair{Key: types.CollateralRatioIndexPrefix, Value: sdk.Uint64ToBigEndian(10)}, cmn.KVPair{Key: []byte(types.DebtDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, @@ -49,6 +52,7 @@ func TestDecodeDistributionStore(t *testing.T) { expectedLog string }{ {"CdpIDs", fmt.Sprintf("%v\n%v", cdpIds, cdpIds)}, + {"CDP", fmt.Sprintf("%v\n%v", cdp, cdp)}, {"CdpID", "2\n2"}, {"CollateralRatioIndex", "10\n10"}, {"DebtDenom", fmt.Sprintf("%s\n%s", denom, denom)}, diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index bd769662..393643a7 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -175,6 +175,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current } var price types.CurrentPrice k.cdc.MustUnmarshalBinaryBare(bz, &price) + if price.Price.Equal(sdk.ZeroDec()) { return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) } diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index aaa01e9e..9693b6e3 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -20,7 +20,7 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { return fmt.Sprintf("%s\n%s", priceA, priceB) case bytes.Contains(kvA.Key, []byte(types.RawPriceFeedPrefix)): - var postedPriceA, postedPriceB types.PostedPrice + var postedPriceA, postedPriceB []types.PostedPrice cdc.MustUnmarshalBinaryBare(kvA.Value, &postedPriceA) cdc.MustUnmarshalBinaryBare(kvB.Value, &postedPriceB) return fmt.Sprintf("%s\n%s", postedPriceA, postedPriceB) diff --git a/x/pricefeed/simulation/decoder_test.go b/x/pricefeed/simulation/decoder_test.go index 2cc846ec..bc886819 100644 --- a/x/pricefeed/simulation/decoder_test.go +++ b/x/pricefeed/simulation/decoder_test.go @@ -27,7 +27,7 @@ func TestDecodeDistributionStore(t *testing.T) { cdc := makeTestCodec() currentPrice := types.CurrentPrice{MarketID: "current", Price: sdk.OneDec()} - postedPrice := types.PostedPrice{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()} + postedPrice := []types.PostedPrice{{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()}} kvPairs := cmn.KVPairs{ cmn.KVPair{Key: []byte(types.CurrentPricePrefix), Value: cdc.MustMarshalBinaryBare(currentPrice)}, diff --git a/x/pricefeed/simulation/genesis.go b/x/pricefeed/simulation/genesis.go index 756fc988..9a84d291 100644 --- a/x/pricefeed/simulation/genesis.go +++ b/x/pricefeed/simulation/genesis.go @@ -2,21 +2,64 @@ package simulation import ( "fmt" + "time" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/kava-labs/kava/x/pricefeed/types" + pricefeed "github.com/kava-labs/kava/x/pricefeed/types" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // RandomizedGenState generates a random GenesisState for pricefeed func RandomizedGenState(simState *module.SimulationState) { - - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - pricefeedGenesis := types.DefaultGenesisState() + // get the params with xrp, btc and bnb to usd + // getPricefeedSimulationParams is defined to return params with xrp:usd, btc:usd, bnb:usd + params := getPricefeedSimulationParams() + markets := []types.Market{} + genPrices := []types.PostedPrice{} + // chose one account to be the oracle + oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))] + for _, market := range params.Markets { + updatedMarket := types.Market{market.MarketID, market.BaseAsset, market.QuoteAsset, []sdk.AccAddress{oracle.Address}, true} + markets = append(markets, updatedMarket) + genPrice := types.PostedPrice{market.MarketID, oracle.Address, getInitialPrice(market.MarketID), simState.GenTimestamp.Add(time.Hour * 24)} + genPrices = append(genPrices, genPrice) + } + params = types.NewParams(markets) + pricefeedGenesis := types.NewGenesisState(params, genPrices) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis) } + +// getPricefeedSimulationParams returns the params with xrp:usd, btc:usd, bnb:usd +func getPricefeedSimulationParams() types.Params { + // SET UP THE PRICEFEED GENESIS STATE + pricefeedGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + } + return pricefeedGenesis.Params +} + +// getInitialPrice gets the starting price for each of the base assets +func getInitialPrice(marketId string) (price sdk.Dec) { + switch marketId { + case "btc:usd": + return sdk.MustNewDecFromStr("7000") + case "bnb:usd": + return sdk.MustNewDecFromStr("14") + case "xrp:usd": + return sdk.MustNewDecFromStr("0.2") + } + panic(fmt.Sprintf("Invalid marketId in getInitialPrice: %s\n", marketId)) +} diff --git a/x/pricefeed/simulation/operations/msg.go b/x/pricefeed/simulation/operations/msg.go new file mode 100644 index 00000000..bcac646d --- /dev/null +++ b/x/pricefeed/simulation/operations/msg.go @@ -0,0 +1,136 @@ +package operations + +import ( + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/pricefeed" + "github.com/kava-labs/kava/x/pricefeed/keeper" + "github.com/kava-labs/kava/x/pricefeed/types" +) + +var ( + noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName) +) + +// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price +func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation { + // get a pricefeed handler + handler := pricefeed.NewHandler(keeper) + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + // OVERALL LOGIC: + // (1) RANDOMLY PICK AN ASSET OUT OF BNB AN BTC [TODO QUESTION - USDX IS EXCLUDED AS IT IS A STABLE DENOM + // (2) GET THE CURRENT PRICE OF THAT ASSET IN USD + // (3) GENERATE A RANDOM NUMBER IN THE RANGE 0.8-1.2 (UNIFORM DISTRIBUTION) + // (4) MULTIPLY THE CURRENT PRICE BY THE RANDOM NUMBER + // (5) POST THE NEW PRICE TO THE KEEPER + + // pick a random asset out of BNB and BTC + randomMarket := pickRandomAsset(ctx, keeper, r) + + marketID := randomMarket.MarketID + + // Get the current price of the asset + currentPrice, err := keeper.GetCurrentPrice(ctx, marketID) // Note this is marketID AND **NOT** just the base asset + if err != nil { + return noOpMsg, nil, fmt.Errorf("Error getting current price") + } + + // get the address for the account + // this address needs to be an oracle and also exist. genesis should add all the accounts as oracles. + address := getRandomOracle(r, randomMarket) + + // generate a new random price based off the current price + price, err := pickNewRandomPrice(r, currentPrice.Price) + if err != nil { + return noOpMsg, nil, fmt.Errorf("Error picking random price") + } + + // get the expiry time based off the current time + expiry := getExpiryTime(ctx) + + // now create the msg to post price + msg := types.NewMsgPostPrice(address, marketID, price, expiry) + + // Perform basic validation of the msg - don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic + if err := msg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + // now we submit the pricefeed update message + if ok := submitMsg(ctx, handler, msg); !ok { + return noOpMsg, nil, fmt.Errorf("could not submit pricefeed msg") + } + return simulation.NewOperationMsg(msg, true, "pricefeed update submitted"), nil, nil + } +} + +// getRandomOracle picks a random oracle from the list of oracles +func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress { + randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles)) + oracle := market.Oracles[randomIndex] + return oracle +} + +// 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)) + market = params.Markets[randomIndex] + return market +} + +// 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 + t = ctx.BlockTime().Add(time.Second * 1000000) + return t +} + +// pickNewRandomPrice picks a new random price given the current price +// It takes the current price then generates a random number to multiply it by to create variation while +// still being in the similar range. Random walk style. +func pickNewRandomPrice(r *rand.Rand, currentPrice sdk.Dec) (price sdk.Dec, err sdk.Error) { + // Pick random price + // this is in the range [0-0.4) because when added to 0.8 it gives a multiplier in the range 0.8-1.2 + got := sdk.MustNewDecFromStr("0.4") + + randomPriceMultiplier := simulation.RandomDecAmount(r, got) // get a random number + if err != nil { + fmt.Errorf("Error generating random price multiplier\n") + return sdk.ZeroDec(), err + } + // 0.8 offset corresponds to 80% of the the current price + offset := sdk.MustNewDecFromStr("0.8") + + // gives a result in range 0.8-1.2 inclusive, so the price can fluctuate from 80% to 120% of its current value + randomPriceMultiplier = randomPriceMultiplier.Add(offset) + + // multiply the current price by the price multiplier + price = randomPriceMultiplier.Mul(currentPrice) + // return the price + return price, nil +} + +// submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not +func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { + ctx, write := ctx.CacheContext() + got := handler(ctx, msg) + + ok = got.IsOK() + if ok { + write() + } + return ok +} diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index 43a4f0b8..bd896d56 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -7,7 +7,7 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" + "github.com/kava-labs/kava/x/validator-vesting/keeper" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index b2e65667..dcac8266 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -11,8 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func TestBeginBlockerZeroHeight(t *testing.T) { diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 9246d614..5ddc76e1 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -1,13 +1,13 @@ // nolint // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/keeper -// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/types +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/keeper +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/types package validatorvesting import ( - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) const ( diff --git a/x/validator-vesting/client/cli/query.go b/x/validator-vesting/client/cli/query.go index 5b13bff7..ce00168f 100644 --- a/x/validator-vesting/client/cli/query.go +++ b/x/validator-vesting/client/cli/query.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" diff --git a/x/validator-vesting/client/rest/query.go b/x/validator-vesting/client/rest/query.go index 3dbf8a3e..ebe1de2e 100644 --- a/x/validator-vesting/client/rest/query.go +++ b/x/validator-vesting/client/rest/query.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/gorilla/mux" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // define routes that get registered by the main application diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index d3e463c6..e987b00c 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -2,7 +2,7 @@ package validatorvesting import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup. diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/keeper/keeper.go similarity index 99% rename from x/validator-vesting/internal/keeper/keeper.go rename to x/validator-vesting/keeper/keeper.go index bff59ee4..54662879 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/keeper/keeper.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" "github.com/tendermint/tendermint/libs/log" ) diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/keeper/keeper_test.go similarity index 99% rename from x/validator-vesting/internal/keeper/keeper_test.go rename to x/validator-vesting/keeper/keeper_test.go index cedf77d3..51d940ed 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/keeper/keeper_test.go @@ -11,7 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func TestGetSetValidatorVestingAccounts(t *testing.T) { diff --git a/x/validator-vesting/internal/keeper/querier.go b/x/validator-vesting/keeper/querier.go similarity index 97% rename from x/validator-vesting/internal/keeper/querier.go rename to x/validator-vesting/keeper/querier.go index dafc5f5b..32c7a11e 100644 --- a/x/validator-vesting/internal/keeper/querier.go +++ b/x/validator-vesting/keeper/querier.go @@ -4,7 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/auth/vesting" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/keeper/test_common.go similarity index 99% rename from x/validator-vesting/internal/keeper/test_common.go rename to x/validator-vesting/keeper/test_common.go index 8c630329..36ba430e 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/keeper/test_common.go @@ -25,7 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) //nolint: deadcode unused diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 16a081e4..903e8b8a 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -15,8 +15,8 @@ import ( sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/kava-labs/kava/x/validator-vesting/client/cli" "github.com/kava-labs/kava/x/validator-vesting/client/rest" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" "github.com/kava-labs/kava/x/validator-vesting/simulation" + "github.com/kava-labs/kava/x/validator-vesting/types" ) var ( diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go index a09bbd81..445828a6 100644 --- a/x/validator-vesting/simulation/decoder.go +++ b/x/validator-vesting/simulation/decoder.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auth type diff --git a/x/validator-vesting/simulation/decoder_test.go b/x/validator-vesting/simulation/decoder_test.go index fee10265..7cb532ca 100644 --- a/x/validator-vesting/simulation/decoder_test.go +++ b/x/validator-vesting/simulation/decoder_test.go @@ -13,7 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func makeTestCodec() (cdc *codec.Codec) { diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index cccdc358..c77bb9a3 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -11,7 +11,7 @@ import ( vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // RandomizedGenState generates a random GenesisState for validator-vesting diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index 909ee0bb..0bc8c141 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -18,8 +18,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) var ( diff --git a/x/validator-vesting/internal/types/codec.go b/x/validator-vesting/types/codec.go similarity index 100% rename from x/validator-vesting/internal/types/codec.go rename to x/validator-vesting/types/codec.go diff --git a/x/validator-vesting/internal/types/expected_keepers.go b/x/validator-vesting/types/expected_keepers.go similarity index 100% rename from x/validator-vesting/internal/types/expected_keepers.go rename to x/validator-vesting/types/expected_keepers.go diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/types/genesis.go similarity index 100% rename from x/validator-vesting/internal/types/genesis.go rename to x/validator-vesting/types/genesis.go diff --git a/x/validator-vesting/internal/types/key.go b/x/validator-vesting/types/key.go similarity index 100% rename from x/validator-vesting/internal/types/key.go rename to x/validator-vesting/types/key.go diff --git a/x/validator-vesting/internal/types/querier.go b/x/validator-vesting/types/querier.go similarity index 100% rename from x/validator-vesting/internal/types/querier.go rename to x/validator-vesting/types/querier.go diff --git a/x/validator-vesting/internal/types/test_common.go b/x/validator-vesting/types/test_common.go similarity index 100% rename from x/validator-vesting/internal/types/test_common.go rename to x/validator-vesting/types/test_common.go diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/types/validator_vesting_account.go similarity index 100% rename from x/validator-vesting/internal/types/validator_vesting_account.go rename to x/validator-vesting/types/validator_vesting_account.go diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/types/validator_vesting_account_test.go similarity index 100% rename from x/validator-vesting/internal/types/validator_vesting_account_test.go rename to x/validator-vesting/types/validator_vesting_account_test.go