From b969a0ea33a5f4f1d8da25d9e4fb8f0093d87848 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Fri, 24 Apr 2020 14:55:18 -0700 Subject: [PATCH] Incentive module simulations (#439) * Incentive module simulations (#439) Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis Co-authored-by: Kevin Davis Co-authored-by: John Maheswaran --- app/app.go | 4 +- app/params/params.go | 1 + app/sim_test.go | 2 +- run_a_bunch_of_sims.sh | 5 - x/cdp/simulation/genesis.go | 1 + x/incentive/module.go | 13 ++- x/incentive/simulation/decoder.go | 44 +++++++- x/incentive/simulation/decoder_test.go | 65 +++++++++++ x/incentive/simulation/genesis.go | 140 ++++++++++++++++++++++++ x/incentive/simulation/operations.go | 146 +++++++++++++++++++++++++ x/incentive/simulation/params.go | 36 +++++- x/incentive/simulation/simulation.go | 22 ---- x/kavadist/simulation/genesis.go | 22 ++-- 13 files changed, 448 insertions(+), 53 deletions(-) delete mode 100755 run_a_bunch_of_sims.sh create mode 100644 x/incentive/simulation/decoder_test.go create mode 100644 x/incentive/simulation/genesis.go create mode 100644 x/incentive/simulation/operations.go delete mode 100644 x/incentive/simulation/simulation.go diff --git a/app/app.go b/app/app.go index da424542..37599d15 100644 --- a/app/app.go +++ b/app/app.go @@ -333,7 +333,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), - incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -376,7 +376,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), - incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper), ) app.sm.RegisterStoreDecoders() diff --git a/app/params/params.go b/app/params/params.go index d74f7623..17138c3d 100644 --- a/app/params/params.go +++ b/app/params/params.go @@ -12,4 +12,5 @@ const ( DefaultWeightMsgCreateAtomicSwap int = 100 DefaultWeightMsgUpdatePrices int = 100 DefaultWeightMsgCdp int = 100 + DefaultWeightMsgClaimReward int = 100 ) diff --git a/app/sim_test.go b/app/sim_test.go index d7a48802..4081978a 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -284,4 +284,4 @@ func TestAppStateDeterminism(t *testing.T) { ) } } -} +} \ No newline at end of file diff --git a/run_a_bunch_of_sims.sh b/run_a_bunch_of_sims.sh deleted file mode 100755 index 3fe1f5d7..00000000 --- a/run_a_bunch_of_sims.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for i in {1..10}; do - go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed ${i} -v -timeout 24h -done \ No newline at end of file diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index 65f568aa..655e7018 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -32,6 +32,7 @@ func RandomizedGenState(simState *module.SimulationState) { sdk.NewCoin("xrp", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))), sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))), sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))), + sdk.NewCoin("ukava", sdk.NewInt(int64(simState.Rand.Intn(500000000000)))), ) err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd...)) if err != nil { diff --git a/x/incentive/module.go b/x/incentive/module.go index 623f883e..9c464e00 100644 --- a/x/incentive/module.go +++ b/x/incentive/module.go @@ -8,6 +8,7 @@ import ( "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" sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/gorilla/mux" "github.com/kava-labs/kava/x/incentive/client/cli" @@ -90,23 +91,25 @@ func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedP } // WeightedOperations returns the all the bep3 module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation { - return nil +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.supplyKeeper, am.keeper) } // AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic - keeper keeper.Keeper - supplyKeeper types.SupplyKeeper + keeper Keeper + accountKeeper auth.AccountKeeper + supplyKeeper SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper keeper.Keeper, supplyKeeper types.SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, supplyKeeper: supplyKeeper, } } diff --git a/x/incentive/simulation/decoder.go b/x/incentive/simulation/decoder.go index ace82b35..dda10310 100644 --- a/x/incentive/simulation/decoder.go +++ b/x/incentive/simulation/decoder.go @@ -1,12 +1,50 @@ package simulation import ( + "bytes" + "encoding/binary" + "fmt" + "time" + "github.com/cosmos/cosmos-sdk/codec" "github.com/tendermint/tendermint/libs/kv" + + "github.com/kava-labs/kava/x/incentive/types" ) -// DecodeStore unmarshals the KVPair's Value to the corresponding incentive type +// DecodeStore unmarshals the KVPair's Value to the module's corresponding type func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { - // TODO implement this - return "" + switch { + case bytes.Equal(kvA.Key[:1], types.RewardPeriodKeyPrefix): + var rewardPeriodA, rewardPeriodB types.RewardPeriod + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &rewardPeriodA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &rewardPeriodB) + return fmt.Sprintf("%v\n%v", rewardPeriodA, rewardPeriodB) + + case bytes.Equal(kvA.Key[:1], types.ClaimPeriodKeyPrefix): + var claimPeriodA, claimPeriodB types.ClaimPeriod + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimPeriodA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimPeriodB) + return fmt.Sprintf("%v\n%v", claimPeriodA, claimPeriodB) + + case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix): + var claimA, claimB types.Claim + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimB) + return fmt.Sprintf("%v\n%v", claimA, claimB) + + case bytes.Equal(kvA.Key[:1], types.NextClaimPeriodIDPrefix): + claimPeriodIDA := binary.BigEndian.Uint64(kvA.Value) + claimPeriodIDB := binary.BigEndian.Uint64(kvB.Value) + return fmt.Sprintf("%d\n%d", claimPeriodIDA, claimPeriodIDB) + + case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey): + var timeA, timeB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB) + return fmt.Sprintf("%s\n%s", timeA, timeB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/incentive/simulation/decoder_test.go b/x/incentive/simulation/decoder_test.go new file mode 100644 index 00000000..7eb08ebd --- /dev/null +++ b/x/incentive/simulation/decoder_test.go @@ -0,0 +1,65 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/kv" + + "github.com/kava-labs/kava/x/incentive/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + // Set up RewardPeriod, ClaimPeriod, Claim, and previous block time + rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(), + sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), time.Duration(time.Hour*2)) + claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), time.Duration(time.Hour*24)) + addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw") + claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1) + prevBlockTime := time.Now().Add(time.Hour * -1).UTC() + + kvPairs := kv.Pairs{ + kv.Pair{Key: types.RewardPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&rewardPeriod)}, + kv.Pair{Key: types.ClaimPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claimPeriod)}, + kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claim)}, + kv.Pair{Key: types.NextClaimPeriodIDPrefix, Value: sdk.Uint64ToBigEndian(10)}, + kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"RewardPeriod", fmt.Sprintf("%v\n%v", rewardPeriod, rewardPeriod)}, + {"ClaimPeriod", fmt.Sprintf("%v\n%v", claimPeriod, claimPeriod)}, + {"Claim", fmt.Sprintf("%v\n%v", claim, claim)}, + {"NextClaimPeriodID", "10\n10"}, + {"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/incentive/simulation/genesis.go b/x/incentive/simulation/genesis.go new file mode 100644 index 00000000..88a28bd6 --- /dev/null +++ b/x/incentive/simulation/genesis.go @@ -0,0 +1,140 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/incentive/types" +) + +var ( + CollateralDenoms = [3]string{"bnb", "xrp", "btc"} + RewardDenom = "ukava" + MaxTotalAssetReward = sdk.NewInt(1000000000) +) + +// RandomizedGenState generates a random GenesisState for incentive module +func RandomizedGenState(simState *module.SimulationState) { + params := genParams(simState.Rand) + rewardPeriods := genRewardPeriods(simState.Rand, simState.GenTimestamp, params.Rewards) + claimPeriods := genClaimPeriods(rewardPeriods) + claimPeriodIDs := genNextClaimPeriodIds(claimPeriods) + + // New genesis state holds valid, linked reward periods, claim periods, and claim period IDs + incentiveGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime, + rewardPeriods, claimPeriods, types.Claims{}, claimPeriodIDs) + if err := incentiveGenesis.Validate(); err != nil { + panic(err) + } + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, incentiveGenesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(incentiveGenesis) +} + +// genParams generates random rewards and is active by default +func genParams(r *rand.Rand) types.Params { + params := types.NewParams(true, genRewards(r)) + if err := params.Validate(); err != nil { + panic(err) + } + return params +} + +// genRewards generates rewards for each specified collateral type +func genRewards(r *rand.Rand) types.Rewards { + var rewards types.Rewards + for _, denom := range CollateralDenoms { + active := true + // total reward is in range (half max total reward, max total reward) + amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64())) + totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount)) + // generate a random number of hours between 6-48 to use for reward's times + numbHours := simulation.RandIntBetween(r, 6, 48) + duration := time.Duration(time.Hour * time.Duration(numbHours)) + timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration + claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration + reward := types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration) + rewards = append(rewards, reward) + } + return rewards +} + +// genRewardPeriods generates chronological reward periods for each given reward type +func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods { + var rewardPeriods types.RewardPeriods + for _, reward := range rewards { + rewardPeriodStart := timestamp + for i := 10; i >= simulation.RandIntBetween(r, 2, 9); i-- { + // Set up reward period parameters + start := rewardPeriodStart + end := start.Add(reward.Duration).UTC() + baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward + // Earlier periods have larger rewards + amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i)))) + claimEnd := end.Add(reward.ClaimDuration) + claimTimeLock := reward.TimeLock + // Create reward period and append to array + rewardPeriod := types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock) + rewardPeriods = append(rewardPeriods, rewardPeriod) + // Update start time of next reward period + rewardPeriodStart = end + } + } + return rewardPeriods +} + +// genClaimPeriods loads valid claim periods for an array of reward periods +func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods { + denomRewardPeriodsCount := make(map[string]uint64) + var claimPeriods types.ClaimPeriods + for _, rewardPeriod := range rewardPeriods { + // Increment reward period count for this denom (this is our claim period's ID) + denom := rewardPeriod.Denom + numbRewardPeriods := denomRewardPeriodsCount[denom] + 1 + denomRewardPeriodsCount[denom] = numbRewardPeriods + // Set end and timelock from the associated reward period + end := rewardPeriod.ClaimEnd + claimTimeLock := rewardPeriod.ClaimTimeLock + // Create the new claim period for this reward period + claimPeriod := types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock) + claimPeriods = append(claimPeriods, claimPeriod) + } + return claimPeriods +} + +// genNextClaimPeriodIds returns an array of the most recent claim period IDs for each denom +func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs { + // Build a map of the most recent claim periods by denom + mostRecentClaimPeriodByDenom := make(map[string]uint64) + for _, cp := range cps { + if cp.ID > mostRecentClaimPeriodByDenom[cp.Denom] { + mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID + } + } + // Write map contents to an array of GenesisClaimPeriodIDs + var claimPeriodIDs types.GenesisClaimPeriodIDs + for key, value := range mostRecentClaimPeriodByDenom { + claimPeriodID := types.GenesisClaimPeriodID{Denom: key, ID: value} + claimPeriodIDs = append(claimPeriodIDs, claimPeriodID) + } + return claimPeriodIDs +} + +// 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/incentive/simulation/operations.go b/x/incentive/simulation/operations.go new file mode 100644 index 00000000..03e143d8 --- /dev/null +++ b/x/incentive/simulation/operations.go @@ -0,0 +1,146 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "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" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/cosmos/cosmos-sdk/x/simulation" + + appparams "github.com/kava-labs/kava/app/params" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/kavadist" +) + +// Simulation operation weights constants +const ( + OpWeightMsgClaimReward = "op_weight_msg_claim_reward" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgClaimReward int + + appParams.GetOrGenerate(cdc, OpWeightMsgClaimReward, &weightMsgClaimReward, nil, + func(_ *rand.Rand) { + weightMsgClaimReward = appparams.DefaultWeightMsgClaimReward + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgClaimReward, + SimulateMsgClaimReward(ak, sk, k), + ), + } +} + +// SimulateMsgClaimReward generates a MsgClaimReward +func SimulateMsgClaimReward(ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + + // Load only account types that can claim rewards + var accounts []authexported.Account + validAccounts := make(map[string]bool) + for _, acc := range accs { + account := ak.GetAccount(ctx, acc.Address) + switch account.(type) { + case *vesting.PeriodicVestingAccount, *auth.BaseAccount: // Valid: BaseAccount, PeriodicVestingAccount + accounts = append(accounts, account) + validAccounts[account.GetAddress().String()] = true + break + default: // Invalid: ValidatorVestingAccount, DelayedVestingAccount, ContinuousVestingAccount + break + } + } + + // Load open claims and shuffle them to randomize + openClaims := types.Claims{} + k.IterateClaims(ctx, func(claim types.Claim) bool { + openClaims = append(openClaims, claim) + return false + }) + r.Shuffle(len(openClaims), func(i, j int) { + openClaims[i], openClaims[j] = openClaims[j], openClaims[i] + }) + + kavadistMacc := sk.GetModuleAccount(ctx, kavadist.KavaDistMacc) + kavadistBalance := kavadistMacc.SpendableCoins(ctx.BlockTime()) + + // Find address that has a claim of the same reward denom, then confirm it's distributable + claimer, claim, found := findValidAccountClaimPair(accs, openClaims, func(acc simulation.Account, claim types.Claim) bool { + if validAccounts[acc.Address.String()] { // Address must be valid type + if claim.Owner.Equals(acc.Address) { // Account must be claim owner + allClaims, found := k.GetClaimsByAddressAndDenom(ctx, claim.Owner, claim.Denom) + if found { // found should always be true + var rewards sdk.Coins + for _, individualClaim := range allClaims { + rewards = rewards.Add(individualClaim.Reward) + } + if rewards.AmountOf(claim.Reward.Denom).IsPositive() { // Can't distribute 0 coins + // Validate that kavadist module has enough coins to distribute rewards + if kavadistBalance.AmountOf(claim.Reward.Denom).GTE(rewards.AmountOf(claim.Reward.Denom)) { + return true + } + } + } + } + } + return false + }) + if !found { + return simulation.NewOperationMsgBasic(types.ModuleName, + "no-operation (no accounts currently have fulfillable claims)", "", false, nil), nil, nil + } + + claimerAcc := ak.GetAccount(ctx, claimer.Address) + if claimerAcc == nil { + return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address) + } + + msg := types.NewMsgClaimReward(claimer.Address, claim.Denom) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.NewCoins(), + helpers.DefaultGenTxGas, + chainID, + []uint64{claimerAcc.GetAccountNumber()}, + []uint64{claimerAcc.GetSequence()}, + claimer.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + // to aid debugging, add the stack trace to the comment field of the returned opMsg + return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err + } + + // to aid debugging, add the result log to the comment field + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil + } +} + +// findValidAccountClaimPair finds an account and reward claim for which the callback func returns true +func findValidAccountClaimPair(accounts []simulation.Account, claims types.Claims, + cb func(simulation.Account, types.Claim) bool) (simulation.Account, types.Claim, bool) { + for _, claim := range claims { + for _, acc := range accounts { + if isValid := cb(acc, claim); isValid { + return acc, claim, true + } + } + } + return simulation.Account{}, types.Claim{}, false +} diff --git a/x/incentive/simulation/params.go b/x/incentive/simulation/params.go index 8c1f7aff..dd47e83f 100644 --- a/x/incentive/simulation/params.go +++ b/x/incentive/simulation/params.go @@ -1,14 +1,40 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/kava-labs/kava/x/incentive/types" ) -// 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{} +const ( + keyActive = "Active" + keyRewards = "Rewards" +) + +// genActive generates active bool with 80% chance of true +func genActive(r *rand.Rand) bool { + threshold := 80 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return false + } + return true +} + +// ParamChanges defines the parameters that can be modified by param change proposals +func ParamChanges(r *rand.Rand) []simulation.ParamChange { + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, keyActive, + func(r *rand.Rand) string { + return fmt.Sprintf("\"%t\"", genActive(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyRewards, + func(r *rand.Rand) string { + return fmt.Sprintf("\"%v\"", genRewards(r)) + }, + ), + } } diff --git a/x/incentive/simulation/simulation.go b/x/incentive/simulation/simulation.go deleted file mode 100644 index 3057550f..00000000 --- a/x/incentive/simulation/simulation.go +++ /dev/null @@ -1,22 +0,0 @@ -package simulation - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types/module" - - "github.com/kava-labs/kava/x/incentive/types" -) - -// RandomizedGenState generates a random GenesisState for cdp -func RandomizedGenState(simState *module.SimulationState) { - - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - genesis := types.DefaultGenesisState() - - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) -} diff --git a/x/kavadist/simulation/genesis.go b/x/kavadist/simulation/genesis.go index d12a73d5..ebaa2ff7 100644 --- a/x/kavadist/simulation/genesis.go +++ b/x/kavadist/simulation/genesis.go @@ -14,10 +14,11 @@ import ( ) // SecondsPerYear is the number of seconds in a year -const SecondsPerYear = 31536000 - -// BaseAprPadding prevents the calculated SPR inflation rate from being 0.0 -const BaseAprPadding = "0.000000000100000000" +const ( + SecondsPerYear = 31536000 + // BaseAprPadding sets the minimum inflation to the calculated SPR inflation rate from being 0.0 + BaseAprPadding = "0.000000003022265980" +) // RandomizedGenState generates a random GenesisState for kavadist module func RandomizedGenState(simState *module.SimulationState) { @@ -46,8 +47,8 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods { numPeriods := simulation.RandIntBetween(r, 1, 10) periodStart := timestamp for i := 0; i < numPeriods; i++ { - // set periods to be between 2 weeks and 2 years - durationMultiplier := simulation.RandIntBetween(r, 14, 104) + // set periods to be between 1-3 days + durationMultiplier := simulation.RandIntBetween(r, 1, 3) duration := time.Duration(int64(24*durationMultiplier)) * time.Hour periodEnd := periodStart.Add(duration) inflation := genRandomInflation(r) @@ -59,13 +60,14 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods { } func genRandomInflation(r *rand.Rand) sdk.Dec { - // If sim.RandomDecAmount returns 0 (happens frequently by design), add BaseAprPadding + // If sim.RandomDecAmount is less than base apr padding, add base apr padding + aprPadding, _ := sdk.NewDecFromStr(BaseAprPadding) extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25")) - for extraAprInflation.Equal(sdk.ZeroDec()) { - extraAprInflation = extraAprInflation.Add(sdk.MustNewDecFromStr(BaseAprPadding)) + for extraAprInflation.LT(aprPadding) { + extraAprInflation = extraAprInflation.Add(aprPadding) } - aprInflation := sdk.OneDec().Add(extraAprInflation) + // convert APR inflation to SPR (inflation per second) inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear)) if err != nil {