From 915de21a7c0c59337b497e68fc289284ddff5444 Mon Sep 17 00:00:00 2001 From: kogisin Date: Wed, 18 Aug 2021 23:25:31 +0900 Subject: [PATCH 01/31] test: add sim_test --- app/sim_test.go | 339 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 app/sim_test.go diff --git a/app/sim_test.go b/app/sim_test.go new file mode 100644 index 00000000..66baec60 --- /dev/null +++ b/app/sim_test.go @@ -0,0 +1,339 @@ +package app + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + farmingtypes "github.com/tendermint/farming/x/farming/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" +) + +// Get flags every time the simulator is run +func init() { + simapp.GetSimulatorFlags() +} + +type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte +} + +// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of +// an IAVLStore for faster simulation speed. +func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} + +// interBlockCacheOpt returns a BaseApp option function that sets the persistent +// inter-block write-through cache. +func interBlockCacheOpt() func(*baseapp.BaseApp) { + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) +} + +func TestFullAppSimulation(t *testing.T) { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + db.Close() + require.NoError(t, os.RemoveAll(dir)) + }() + + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + // run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // replace with own random account function if using keys other than secp256k1 + simapp.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simapp.PrintStats(db) + } +} + +func TestAppImportExport(t *testing.T) { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { + t.Skip("skipping application import/export simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + db.Close() + require.NoError(t, os.RemoveAll(dir)) + }() + + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + // run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // replace with own random account function if using keys other than secp256k1 + simapp.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simapp.PrintStats(db) + } + + fmt.Printf("exporting genesis...\n") + + exported, err := app.ExportAppStateAndValidators(false, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") + require.NoError(t, err, "simulation setup failed") + + defer func() { + newDB.Close() + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + var genesisState GenesisState + err = json.Unmarshal(exported.AppState, &genesisState) + require.NoError(t, err) + + ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + newApp.mm.InitGenesis(ctxB, app.AppCodec(), genesisState) + newApp.StoreConsensusParams(ctxB, exported.ConsensusParams) + + fmt.Printf("comparing stores...\n") + + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.keys[authtypes.StoreKey], newApp.keys[authtypes.StoreKey], [][]byte{}}, + {app.keys[stakingtypes.StoreKey], newApp.keys[stakingtypes.StoreKey], + [][]byte{ + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, + stakingtypes.HistoricalInfoKey, + }}, // ordering may change but it doesn't matter + {app.keys[slashingtypes.StoreKey], newApp.keys[slashingtypes.StoreKey], [][]byte{}}, + {app.keys[minttypes.StoreKey], newApp.keys[minttypes.StoreKey], [][]byte{}}, + {app.keys[distrtypes.StoreKey], newApp.keys[distrtypes.StoreKey], [][]byte{}}, + {app.keys[banktypes.StoreKey], newApp.keys[banktypes.StoreKey], [][]byte{banktypes.BalancesPrefix}}, + {app.keys[paramtypes.StoreKey], newApp.keys[paramtypes.StoreKey], [][]byte{}}, + {app.keys[govtypes.StoreKey], newApp.keys[govtypes.StoreKey], [][]byte{}}, + {app.keys[evidencetypes.StoreKey], newApp.keys[evidencetypes.StoreKey], [][]byte{}}, + {app.keys[capabilitytypes.StoreKey], newApp.keys[capabilitytypes.StoreKey], [][]byte{}}, + {app.keys[authzkeeper.StoreKey], newApp.keys[authzkeeper.StoreKey], [][]byte{}}, + {app.keys[farmingtypes.StoreKey], newApp.keys[farmingtypes.StoreKey], [][]byte{}}, + } + + for _, skp := range storeKeysPrefixes { + storeA := ctxA.KVStore(skp.A) + storeB := ctxB.KVStore(skp.B) + + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) + } +} + +func TestAppSimulationAfterImport(t *testing.T) { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { + t.Skip("skipping application simulation after import") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + db.Close() + require.NoError(t, os.RemoveAll(dir)) + }() + + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + // run randomized simulation + stopEarly, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // replace with own random account function if using keys other than secp256k1 + simapp.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simapp.PrintStats(db) + } + + if stopEarly { + fmt.Println("can't export or import a zero-validator genesis, exiting test...") + return + } + + fmt.Printf("exporting genesis...\n") + + exported, err := app.ExportAppStateAndValidators(true, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") + require.NoError(t, err, "simulation setup failed") + + defer func() { + newDB.Close() + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + newApp.InitChain(abci.RequestInitChain{ + AppStateBytes: exported.AppState, + }) + + _, _, err = simulation.SimulateFromSeed( + t, + os.Stdout, + newApp.BaseApp, + simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simapp.SimulationOperations(newApp, newApp.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + require.NoError(t, err) +} + +func TestAppStateDeterminism(t *testing.T) { + if !simapp.FlagEnabledValue { + t.Skip("skipping application simulation") + } + + config := simapp.NewConfigFromFlags() + config.InitialBlockHeight = 1 + config.ExportParamsPath = "" + config.OnOperation = false + config.AllInvariants = false + config.ChainID = helpers.SimAppChainID + + numSeeds := 1 + numTimesToRunPerSeed := 1 + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + + for i := 0; i < numSeeds; i++ { + config.Seed = rand.Int63() + + for j := 0; j < numTimesToRunPerSeed; j++ { + var logger log.Logger + if simapp.FlagVerboseValue { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + + db := dbm.NewMemDB() + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + + fmt.Printf( + "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + ) + + _, _, err := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simapp.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + require.NoError(t, err) + + if config.Commit { + simapp.PrintStats(db) + } + + appHash := app.LastCommitID().Hash + appHashList[j] = appHash + + if j != 0 { + require.Equal( + t, string(appHashList[0]), string(appHashList[j]), + "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + ) + } + } + } +} From 177e0f273933464e891576d8c1c7bd6c67d5981d Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 19 Aug 2021 15:08:09 +0900 Subject: [PATCH 02/31] test: add decoder and its unit tests --- x/farming/simulation/decoder.go | 7 ++- x/farming/simulation/decoder_test.go | 86 ++++++++++++++++------------ 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/x/farming/simulation/decoder.go b/x/farming/simulation/decoder.go index 1d744ea1..4920906c 100644 --- a/x/farming/simulation/decoder.go +++ b/x/farming/simulation/decoder.go @@ -22,13 +22,16 @@ func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { cdc.MustUnmarshal(kvA.Value, &pB) return fmt.Sprintf("%v\n%v", pA, pB) - case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix), + bytes.Equal(kvA.Key[:1], types.StakingByFarmerIndexKeyPrefix), + bytes.Equal(kvA.Key[:1], types.StakingsByStakingCoinDenomIndexKeyPrefix): var sA, sB types.Staking cdc.MustUnmarshal(kvA.Value, &sA) cdc.MustUnmarshal(kvA.Value, &sB) return fmt.Sprintf("%v\n%v", sA, sB) - case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix), + bytes.Equal(kvA.Key[:1], types.RewardsByFarmerIndexKeyPrefix): var rA, rB types.Reward cdc.MustUnmarshal(kvA.Value, &rA) return fmt.Sprintf("%v\n%v", rA, rB) diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index d0eae7c7..fddac2fe 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -1,49 +1,61 @@ package simulation_test import ( + "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/types/kv" "github.com/tendermint/farming/x/farming/simulation" + "github.com/tendermint/farming/x/farming/types" ) func TestDecodeFarmingStore(t *testing.T) { - - cdc := simapp.MakeTestEncodingConfig() - _ = simulation.NewDecodeStore(cdc.Marshaler) - - // TODO: not implemented yet - - // liquidityPool := types.Pool{} - // liquidityPool.Id = 1 - // liquidityPoolBatch := types.NewPoolBatch(1, 1) - - // kvPairs := kv.Pairs{ - // Pairs: []kv.Pair{ - // {Key: types.PoolKeyPrefix, Value: cdc.MustMarshalBinaryBare(&liquidityPool)}, - // {Key: types.PoolBatchKeyPrefix, Value: cdc.MustMarshalBinaryBare(&liquidityPoolBatch)}, - // {Key: []byte{0x99}, Value: []byte{0x99}}, - // }, - // } - - // tests := []struct { - // name string - // expectedLog string - // }{ - // {"Pool", fmt.Sprintf("%v\n%v", liquidityPool, liquidityPool)}, - // {"PoolBatch", fmt.Sprintf("%v\n%v", liquidityPoolBatch, liquidityPoolBatch)}, - // {"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() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) - // default: - // require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) - // } - // }) - // } + cdc := simapp.MakeTestEncodingConfig().Marshaler + dec := simulation.NewDecodeStore(cdc) + + basePlan := types.BasePlan{} + staking := types.Staking{} + reward := types.Reward{} + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.PlanKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, + {Key: types.PlansByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, + {Key: types.StakingKeyPrefix, Value: cdc.MustMarshal(&staking)}, + {Key: types.StakingByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&staking)}, + {Key: types.StakingsByStakingCoinDenomIndexKeyPrefix, Value: cdc.MustMarshal(&staking)}, + {Key: types.RewardKeyPrefix, Value: cdc.MustMarshal(&reward)}, + {Key: types.RewardsByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&reward)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Plan", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, + {"Plans", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, + {"Staking", fmt.Sprintf("%v\n%v", staking, staking)}, + {"StakingByFarmer", fmt.Sprintf("%v\n%v", staking, staking)}, + {"Stakings", fmt.Sprintf("%v\n%v", staking, staking)}, + {"Reward", fmt.Sprintf("%v\n%v", reward, reward)}, + {"Rewards", fmt.Sprintf("%v\n%v", reward, reward)}, + {"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() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } } From d0ae1126536262105ca094196239accc5dabd321 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 19 Aug 2021 15:57:43 +0900 Subject: [PATCH 03/31] test: add randomized genesis params --- x/farming/simulation/genesis.go | 36 +++++++++++-- x/farming/simulation/genesis_test.go | 78 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 x/farming/simulation/genesis_test.go diff --git a/x/farming/simulation/genesis.go b/x/farming/simulation/genesis.go index 59fc905b..f5c95606 100644 --- a/x/farming/simulation/genesis.go +++ b/x/farming/simulation/genesis.go @@ -9,22 +9,34 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/tendermint/farming/x/farming/types" ) -// Simulation parameter constants +// Simulation parameter constants. const ( PrivatePlanCreationFee = "private_plan_creation_fee" + StakingCreationFee = "staking_creation_fee" + EpochDays = "epoch_days" ) -// GenPrivatePlanCreationFee return default PrivatePlanCreationFee +// GenPrivatePlanCreationFee return randomized private plan creation fee. func GenPrivatePlanCreationFee(r *rand.Rand) sdk.Coins { - // TODO: randomize private plan creation fee - return types.DefaultPrivatePlanCreationFee + return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 1_000_000_000)))) } -// RandomizedGenState generates a random GenesisState for farming +// GenStakingCreationFee return randomized staking creation fee. +func GenStakingCreationFee(r *rand.Rand) sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 1_000_000_000)))) +} + +// GenEpochDays return default EpochDays. +func GenEpochDays(r *rand.Rand) uint32 { + return types.DefaultEpochDays +} + +// RandomizedGenState generates a random GenesisState for farming. func RandomizedGenState(simState *module.SimulationState) { var privatePlanCreationFee sdk.Coins simState.AppParams.GetOrGenerate( @@ -32,9 +44,23 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { privatePlanCreationFee = GenPrivatePlanCreationFee(r) }, ) + var stakingCreationFee sdk.Coins + simState.AppParams.GetOrGenerate( + simState.Cdc, StakingCreationFee, &stakingCreationFee, simState.Rand, + func(r *rand.Rand) { stakingCreationFee = GenStakingCreationFee(r) }, + ) + + var epochDays uint32 + simState.AppParams.GetOrGenerate( + simState.Cdc, EpochDays, &epochDays, simState.Rand, + func(r *rand.Rand) { epochDays = GenEpochDays(r) }, + ) + farmingGenesis := types.GenesisState{ Params: types.Params{ PrivatePlanCreationFee: privatePlanCreationFee, + StakingCreationFee: stakingCreationFee, + EpochDays: epochDays, }, } diff --git a/x/farming/simulation/genesis_test.go b/x/farming/simulation/genesis_test.go new file mode 100644 index 00000000..3760ed2b --- /dev/null +++ b/x/farming/simulation/genesis_test.go @@ -0,0 +1,78 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/tendermint/farming/x/farming/simulation" + "github.com/tendermint/farming/x/farming/types" +) + +// TestRandomizedGenState tests the normal scenario of applying RandomizedGenState. +// Abnormal scenarios are not tested here. +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + s := rand.NewSource(1) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: 1000, + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(336122540))) + dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(208240456))) + dec3 := uint32(1) + + require.Equal(t, dec1, genState.Params.PrivatePlanCreationFee) + require.Equal(t, dec2, genState.Params.StakingCreationFee) + require.Equal(t, dec3, genState.Params.EpochDays) +} + +// TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. +func TestRandomizedGenState1(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} From c36aa0864503d56a293aa99a8b9a688e5a15c6fe Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 19 Aug 2021 16:02:35 +0900 Subject: [PATCH 04/31] test: add params and unit tests --- x/farming/simulation/params.go | 12 +++++++++++- x/farming/simulation/params_test.go | 7 ++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/x/farming/simulation/params.go b/x/farming/simulation/params.go index 6eb610ab..e3dd0bdf 100644 --- a/x/farming/simulation/params.go +++ b/x/farming/simulation/params.go @@ -13,7 +13,7 @@ import ( ) // ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation +// on the simulation. func ParamChanges(r *rand.Rand) []simtypes.ParamChange { return []simtypes.ParamChange{ simulation.NewSimParamChange(types.ModuleName, string(types.KeyPrivatePlanCreationFee), @@ -21,5 +21,15 @@ func ParamChanges(r *rand.Rand) []simtypes.ParamChange { return fmt.Sprintf("\"%s\"", GenPrivatePlanCreationFee(r)) }, ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyStakingCreationFee), + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", GenStakingCreationFee(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyEpochDays), + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenEpochDays(r)) + }, + ), } } diff --git a/x/farming/simulation/params_test.go b/x/farming/simulation/params_test.go index c3d11341..4e45602e 100644 --- a/x/farming/simulation/params_test.go +++ b/x/farming/simulation/params_test.go @@ -19,12 +19,13 @@ func TestParamChanges(t *testing.T) { simValue string subspace string }{ - {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "\"100000000stake\"", "farming"}, + {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "\"298498081stake\"", "farming"}, + {"farming/StakingCreationFee", "StakingCreationFee", "\"427131847stake\"", "farming"}, + {"farming/EpochDays", "EpochDays", "\"1\"", "farming"}, } paramChanges := simulation.ParamChanges(r) - - require.Len(t, paramChanges, 1) + require.Len(t, paramChanges, 3) for i, p := range paramChanges { require.Equal(t, expected[i].composedKey, p.ComposedKey()) From 0704de1a559137c4cbf6e13c810fb71574ccf9e4 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 19 Aug 2021 19:28:59 +0900 Subject: [PATCH 05/31] chore: adding operations --- Makefile | 8 ++ app/app.go | 4 +- app/params/weights.go | 10 +- x/farming/module.go | 34 +++---- x/farming/simulation/operations.go | 54 ++++++++--- x/farming/simulation/operations_test.go | 116 ++++++++++++++++++++++++ x/farming/types/codec.go | 48 +++++----- 7 files changed, 212 insertions(+), 62 deletions(-) create mode 100644 x/farming/simulation/operations_test.go diff --git a/Makefile b/Makefile index 8e709ad0..90deed22 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ BINDIR ?= $(GOPATH)/bin DOCKER := $(shell which docker) DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf BUILDDIR ?= $(CURDIR)/build +SIMAPP = ./app export GO111MODULE = on @@ -154,6 +155,13 @@ benchmark: .PHONY: test test-all test-unit test-race test-cover test-build +test-sim-nondeterminism: + @echo "Running non-determinism test..." + @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \ + -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h + +.PHONY: test-sim-nondeterminism + ############################################################################### ### Localnet ### ############################################################################### diff --git a/app/app.go b/app/app.go index 037a0cce..992b833f 100644 --- a/app/app.go +++ b/app/app.go @@ -113,9 +113,9 @@ var ( mint.AppModuleBasic{}, distr.AppModuleBasic{}, gov.NewAppModuleBasic( - paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, + paramsclient.ProposalHandler, distrclient.ProposalHandler, + upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, farmingclient.ProposalHandler, - // todo: farming proposal handler ), params.AppModuleBasic{}, crisis.AppModuleBasic{}, diff --git a/app/params/weights.go b/app/params/weights.go index 467626c9..14815ab0 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -2,9 +2,9 @@ package params const ( // TODO: farming determine the weights - DefaultWeightMsgCreateFixedAmountPlan int = 0 - DefaultWeightMsgCreateRatioPlan int = 0 - DefaultWeightMsgStake int = 0 - DefaultWeightMsgUnstake int = 0 - DefaultWeightMsgHarvest int = 0 + DefaultWeightMsgCreateFixedAmountPlan int = 10 + DefaultWeightMsgCreateRatioPlan int = 10 + DefaultWeightMsgStake int = 85 + DefaultWeightMsgUnstake int = 50 + DefaultWeightMsgHarvest int = 30 ) diff --git a/x/farming/module.go b/x/farming/module.go index 9b4d5b7f..b922d30d 100644 --- a/x/farming/module.go +++ b/x/farming/module.go @@ -23,7 +23,7 @@ import ( "github.com/tendermint/farming/x/farming/client/cli" "github.com/tendermint/farming/x/farming/keeper" - //"github.com/tendermint/farming/x/farming/simulation" + "github.com/tendermint/farming/x/farming/simulation" "github.com/tendermint/farming/x/farming/types" ) @@ -184,36 +184,30 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val // GenerateGenesisState creates a randomized GenState of the farming module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - // TODO: implement - //simulation.RandomizedGenState(simState) -} - -// ProposalContents returns all the farming content functions used to -// simulate governance proposals. -func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { - // TODO: implement - return nil - //return simulation.ProposalContents(am.keeper) + simulation.RandomizedGenState(simState) } // RandomizedParams creates randomized farming param changes for the simulator. func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { - return nil - // TODO: implement - //return simulation.ParamChanges(r) + return simulation.ParamChanges(r) } // RegisterStoreDecoder registers a decoder for farming module's types func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// ProposalContents returns all the farming content functions used to +// simulate governance proposals. +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { // TODO: implement - //sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) + // return simulation.ProposalContents(am.keeper) + return nil } // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - // TODO: implement - return nil - //return simulation.WeightedOperations( - // simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, am.stakingKeeper, - //) + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, + ) } diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index a2203bd2..041c8cb3 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -5,6 +5,7 @@ import ( "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" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -14,16 +15,16 @@ import ( "github.com/tendermint/farming/x/farming/types" ) -// Simulation operation weights constants +// Simulation operation weights constants. const ( OpWeightMsgCreateFixedAmountPlan = "op_weight_msg_create_fixed_amount_plan" OpWeightMsgCreateRatioPlan = "op_weight_msg_create_ratio_plan" OpWeightMsgStake = "op_weight_msg_stake" OpWeightMsgUnstake = "op_weight_msg_unstake" - OpWeightMsgClaim = "op_weight_msg_claim" + OpWeightMsgHarvest = "op_weight_msg_harvest" ) -// WeightedOperations returns all the operations from the module with their respective weights +// WeightedOperations returns all the operations from the module with their respective weights. func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, @@ -57,10 +58,10 @@ func WeightedOperations( }, ) - var weightMsgClaim int - appParams.GetOrGenerate(cdc, OpWeightMsgClaim, &weightMsgClaim, nil, + var weightMsgHarvest int + appParams.GetOrGenerate(cdc, OpWeightMsgHarvest, &weightMsgHarvest, nil, func(_ *rand.Rand) { - weightMsgClaim = params.DefaultWeightMsgHarvest + weightMsgHarvest = params.DefaultWeightMsgHarvest }, ) @@ -82,8 +83,8 @@ func WeightedOperations( SimulateMsgUnstake(ak, bk, k), ), simulation.NewWeightedOperation( - weightMsgClaim, - SimulateMsgClaim(ak, bk, k), + weightMsgHarvest, + SimulateMsgHarvest(ak, bk, k), ), } } @@ -94,8 +95,37 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO: not implemented yet - return simtypes.OperationMsg{}, nil, nil + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) + + fees, err := simtypes.RandomFees(r, ctx, spendableCoins) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to generate fees"), nil, err + } + + txGen := params.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenTx( + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + _, _, err = app.Deliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, ""), nil, nil } } @@ -132,9 +162,9 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke } } -// SimulateMsgClaim generates a MsgClaim with random values +// SimulateMsgHarvest generates a MsgHarvest with random values // nolint: interfacer -func SimulateMsgClaim(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { +func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go new file mode 100644 index 00000000..d40d5eb1 --- /dev/null +++ b/x/farming/simulation/operations_test.go @@ -0,0 +1,116 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + farmingapp "github.com/tendermint/farming/app" + farmingparams "github.com/tendermint/farming/app/params" + "github.com/tendermint/farming/x/farming/simulation" + "github.com/tendermint/farming/x/farming/types" +) + +// TestWeightedOperations tests the weights of the operations. +func TestWeightedOperations(t *testing.T) { + app, ctx := createTestApp(false) + + ctx.WithChainID("test-chain") + + cdc := app.AppCodec() + appParams := make(simtypes.AppParams) + + weightedOps := simulation.WeightedOperations(appParams, cdc, app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + + s := rand.NewSource(1) + r := rand.New(s) + accs := simtypes.RandomAccounts(r, 3) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + {farmingparams.DefaultWeightMsgCreateFixedAmountPlan, types.ModuleName, types.TypeMsgCreateFixedAmountPlan}, + {farmingparams.DefaultWeightMsgCreateRatioPlan, types.ModuleName, types.TypeMsgCreateRatioPlan}, + {farmingparams.DefaultWeightMsgStake, types.ModuleName, types.TypeMsgStake}, + {farmingparams.DefaultWeightMsgUnstake, types.ModuleName, types.TypeMsgUnstake}, + {farmingparams.DefaultWeightMsgHarvest, types.ModuleName, types.TypeMsgHarvest}, + } + + for i, w := range weightedOps { + operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) + // the following checks are very much dependent from the ordering of the output given + // by WeightedOperations. if the ordering in WeightedOperations changes some tests + // will fail + require.Equal(t, expected[i].weight, w.Weight(), "weight should be the same") + require.Equal(t, expected[i].opMsgRoute, operationMsg.Route, "route should be the same") + require.Equal(t, expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") + } +} + +// TestSimulateMsgCreateFixedAmountPlan tests the normal scenario of a valid message of type TypeMsgCreateFixedAmountPlan. +// Abnormal scenarios, where the message are created by an errors are not tested here. +func TestSimulateMsgCreateFixedAmountPlan(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // setup randomly generated liquidity pool creation fees + feeCoins := simulation.GenPrivatePlanCreationFee(r) + params := app.FarmingKeeper.GetParams(ctx) + params.PrivatePlanCreationFee = feeCoins + app.FarmingKeeper.SetParams(ctx, params) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgCreateFixedAmountPlan(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgCreateFixedAmountPlan + require.NoError(t, types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg)) + + require.True(t, operationMsg.OK) + require.Equal(t, types.TypeMsgCreateFixedAmountPlan, msg.Type()) + require.Len(t, futureOperations, 0) +} + +func createTestApp(isCheckTx bool) (*farmingapp.FarmingApp, sdk.Context) { + app := farmingapp.Setup(false) + + ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) + app.MintKeeper.SetParams(ctx, minttypes.DefaultParams()) + app.MintKeeper.SetMinter(ctx, minttypes.DefaultInitialMinter()) + + return app, ctx +} + +func getTestingAccounts(t *testing.T, r *rand.Rand, app *farmingapp.FarmingApp, ctx sdk.Context, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200) + initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) + + // add coins to the accounts + for _, account := range accounts { + acc := app.AccountKeeper.NewAccountWithAddress(ctx, account.Address) + app.AccountKeeper.SetAccount(ctx, acc) + err := simapp.FundAccount(app.BankKeeper, ctx, account.Address, initCoins) + require.NoError(t, err) + } + + return accounts +} diff --git a/x/farming/types/codec.go b/x/farming/types/codec.go index debe0c11..1e8a175f 100644 --- a/x/farming/types/codec.go +++ b/x/farming/types/codec.go @@ -1,21 +1,23 @@ package types import ( + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) -//// RegisterLegacyAminoCodec registers the necessary x/farming interfaces and concrete types -//// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. -//func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { -// cdc.RegisterConcrete(&MsgCreateFixedAmountPlan{}, "farming/MsgCreateFixedAmountPlan", nil) -// cdc.RegisterConcrete(&MsgCreateRatioPlan{}, "farming/MsgCreateRatioPlan", nil) -// cdc.RegisterConcrete(&MsgStake{}, "farming/MsgStake", nil) -// cdc.RegisterConcrete(&MsgUnstake{}, "farming/MsgUnstake", nil) -// cdc.RegisterConcrete(&MsgHarvest{}, "farming/MsgHarvest", nil) -//} +// RegisterLegacyAminoCodec registers the necessary x/farming interfaces and concrete types +// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgCreateFixedAmountPlan{}, "farming/MsgCreateFixedAmountPlan", nil) + cdc.RegisterConcrete(&MsgCreateRatioPlan{}, "farming/MsgCreateRatioPlan", nil) + cdc.RegisterConcrete(&MsgStake{}, "farming/MsgStake", nil) + cdc.RegisterConcrete(&MsgUnstake{}, "farming/MsgUnstake", nil) + cdc.RegisterConcrete(&MsgHarvest{}, "farming/MsgHarvest", nil) +} // RegisterInterfaces registers the x/farming interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { @@ -43,20 +45,20 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } -//var ( -// amino = codec.NewLegacyAmino() -// -// // ModuleCdc references the global x/farming module codec. Note, the codec -// // should ONLY be used in certain instances of tests and for JSON encoding as Amino -// // is still used for that purpose. -// // -// // The actual codec used for serialization should be provided to x/farming and -// // defined at the application level. -// ModuleCdc = codec.NewAminoCodec(amino) -//) +var ( + amino = codec.NewLegacyAmino() + + // ModuleCdc references the global x/farming module codec. Note, the codec + // should ONLY be used in certain instances of tests and for JSON encoding as Amino + // is still used for that purpose. + // + // The actual codec used for serialization should be provided to x/farming and + // defined at the application level. + ModuleCdc = codec.NewAminoCodec(amino) +) func init() { - //RegisterLegacyAminoCodec(amino) - //cryptocodec.RegisterCrypto(amino) - //amino.Seal() + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) + amino.Seal() } From ee7e739a7fbb01573e526c6c088e36cc2cb28c40 Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 14:55:13 +0900 Subject: [PATCH 06/31] test: add operations and tests --- x/farming/keeper/reward_test.go | 3 +- x/farming/simulation/operations.go | 211 ++++++++++++++++++++---- x/farming/simulation/operations_test.go | 169 ++++++++++++++++++- x/farming/types/codec.go | 54 +++--- 4 files changed, 371 insertions(+), 66 deletions(-) diff --git a/x/farming/keeper/reward_test.go b/x/farming/keeper/reward_test.go index 2e604473..279a5a12 100644 --- a/x/farming/keeper/reward_test.go +++ b/x/farming/keeper/reward_test.go @@ -3,6 +3,8 @@ package keeper_test import ( "time" + _ "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/farming/x/farming/types" @@ -191,7 +193,6 @@ func (suite *KeeperTestSuite) TestHarvest() { suite.keeper.ProcessQueuedCoins(suite.ctx) balancesBefore := suite.app.BankKeeper.GetAllBalances(suite.ctx, suite.addrs[0]) - suite.ctx = suite.ctx.WithBlockTime(mustParseRFC3339("2021-08-05T00:00:00Z")) err := suite.keeper.DistributeRewards(suite.ctx) suite.Require().NoError(err) diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 041c8cb3..ad600a4e 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -1,11 +1,13 @@ package simulation import ( + "fmt" "math/rand" + "time" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp/helpers" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -98,34 +100,42 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe simAccount, _ := simtypes.RandomAcc(r, accs) account := ak.GetAccount(ctx, simAccount.Address) - spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) - fees, err := simtypes.RandomFees(r, ctx, spendableCoins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to generate fees"), nil, err + params := k.GetParams(ctx) + coins, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + if hasNeg { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "lower balance"), + nil, fmt.Errorf("spendable %s is lower than plan creation fee %s", spendable.String(), coins.String()) } - txGen := params.MakeTestEncodingConfig().TxConfig - tx, err := helpers.GenTx( - txGen, - []sdk.Msg{msg}, - fees, - helpers.DefaultGenTxGas, - chainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - simAccount.PrivKey, + name := "simulation" + creatorAcc := account.GetAddress() + stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) + startTime := time.Now().UTC() + endTime := startTime.AddDate(0, 0, 1) + epochAmount := sdk.NewCoins( + sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 1_000_000_000))), ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err - } - _, _, err = app.Deliver(txGen.TxEncoder(), tx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + msg := types.NewMsgCreateFixedAmountPlan(name, creatorAcc, stakingCoinWeights, startTime, endTime, epochAmount) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, } - return simtypes.NewOperationMsg(msg, true, ""), nil, nil + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -135,8 +145,43 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO: not implemented yet - return simtypes.OperationMsg{}, nil, nil + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + params := k.GetParams(ctx) + coins, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + if hasNeg { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateRatioPlan, "lower balance"), + nil, fmt.Errorf("spendable %s is lower than plan creation fee %s", spendable.String(), coins.String()) + } + + name := "simulation" + creatorAcc := account.GetAddress() + stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) + startTime := time.Now().UTC() + endTime := startTime.AddDate(0, 0, 1) + epochRatio := sdk.NewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 10)), 1) + + msg := types.NewMsgCreateRatioPlan(name, creatorAcc, stakingCoinWeights, startTime, endTime, epochRatio) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -146,8 +191,41 @@ func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO: not implemented yet - return simtypes.OperationMsg{}, nil, nil + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + params := k.GetParams(ctx) + coins, hasNeg := spendable.SafeSub(params.StakingCreationFee) + if hasNeg { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgStake, "lower balance"), + nil, fmt.Errorf("spendable %s is lower than staking creation fee %s", spendable.String(), coins.String()) + } + + farmer := account.GetAddress() + stakingCoins := sdk.NewCoins( + sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 100_000_000))), + ) + + msg := types.NewMsgStake(farmer, stakingCoins) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -157,8 +235,46 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO: not implemented yet - return simtypes.OperationMsg{}, nil, nil + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + farmer := account.GetAddress() + unstakingCoins := sdk.NewCoins( + sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 100_000_000))), + ) + + // staking must exist in order to unharvest + staking, found := k.GetStakingByFarmer(ctx, farmer) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), + nil, fmt.Errorf("staking by %s not found", farmer) + } + + if !staking.StakedCoins.Add(staking.QueuedCoins...).IsAllGTE(unstakingCoins) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), + nil, fmt.Errorf("%s is smaller than %s", staking.StakedCoins.Add(staking.QueuedCoins...).String(), unstakingCoins.String()) + } + + msg := types.NewMsgUnstake(farmer, unstakingCoins) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -168,7 +284,42 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // TODO: not implemented yet - return simtypes.OperationMsg{}, nil, nil + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + farmer := account.GetAddress() + stakingCoinDenoms := []string{sdk.DefaultBondDenom} + + // add a day to increase epoch if there is no harvest rewards + rewards := k.GetRewardsByFarmer(ctx, farmer) + if len(rewards) == 0 { + ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1)) + k.ProcessQueuedCoins(ctx) + k.DistributeRewards(ctx) + k.SetLastEpochTime(ctx, ctx.BlockTime()) + + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "no rewards to harvest"), nil, nil + } + + msg := types.NewMsgHarvest(farmer, stakingCoinDenoms) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendable, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index d40d5eb1..7d520937 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -3,14 +3,16 @@ package simulation_test import ( "math/rand" "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" farmingapp "github.com/tendermint/farming/app" farmingparams "github.com/tendermint/farming/app/params" @@ -27,7 +29,10 @@ func TestWeightedOperations(t *testing.T) { cdc := app.AppCodec() appParams := make(simtypes.AppParams) - weightedOps := simulation.WeightedOperations(appParams, cdc, app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + weightedOps := simulation.WeightedOperations( + appParams, cdc, app.AccountKeeper, + app.BankKeeper, app.FarmingKeeper, + ) s := rand.NewSource(1) r := rand.New(s) @@ -64,9 +69,10 @@ func TestSimulateMsgCreateFixedAmountPlan(t *testing.T) { // setup a single account s := rand.NewSource(1) r := rand.New(s) + accounts := getTestingAccounts(t, r, app, ctx, 1) - // setup randomly generated liquidity pool creation fees + // setup randomly generated private plan creation fees feeCoins := simulation.GenPrivatePlanCreationFee(r) params := app.FarmingKeeper.GetParams(ctx) params.PrivatePlanCreationFee = feeCoins @@ -81,10 +87,151 @@ func TestSimulateMsgCreateFixedAmountPlan(t *testing.T) { require.NoError(t, err) var msg types.MsgCreateFixedAmountPlan - require.NoError(t, types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg)) + err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + require.NoError(t, err) require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgCreateFixedAmountPlan, msg.Type()) + require.Equal(t, "simulation", msg.Name) + require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) + require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) + require.Equal(t, "476941318stake", msg.EpochAmount.String()) + require.Len(t, futureOperations, 0) +} + +// TestSimulateMsgCreateRatioPlan tests the normal scenario of a valid message of type TypeMsgCreateRatioPlan. +// Abnormal scenarios, where the message are created by an errors are not tested here. +func TestSimulateMsgCreateRatioPlan(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // setup randomly generated private plan creation fees + feeCoins := simulation.GenPrivatePlanCreationFee(r) + params := app.FarmingKeeper.GetParams(ctx) + params.PrivatePlanCreationFee = feeCoins + app.FarmingKeeper.SetParams(ctx, params) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgCreateRatioPlan(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgCreateRatioPlan + err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + require.NoError(t, err) + + require.True(t, operationMsg.OK) + require.Equal(t, types.TypeMsgCreateRatioPlan, msg.Type()) + require.Equal(t, "simulation", msg.Name) + require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) + require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) + require.Equal(t, "0.700000000000000000", msg.EpochRatio.String()) + require.Len(t, futureOperations, 0) +} + +// TestSimulateMsgStake tests the normal scenario of a valid message of type TypeMsgStake. +// Abnormal scenarios, where the message are created by an errors are not tested here. +func TestSimulateMsgStake(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // setup randomly generated staking creation fees + feeCoins := simulation.GenStakingCreationFee(r) + params := app.FarmingKeeper.GetParams(ctx) + params.StakingCreationFee = feeCoins + app.FarmingKeeper.SetParams(ctx, params) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgStake(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgStake + err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + require.NoError(t, err) + + require.True(t, operationMsg.OK) + require.Equal(t, types.TypeMsgStake, msg.Type()) + require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) + require.Equal(t, "89941318stake", msg.StakingCoins.String()) + require.Len(t, futureOperations, 0) +} + +// TestSimulateMsgHarvest tests the normal scenario of a valid message of type TypeMsgHarvest. +// Abnormal scenarios, where the message are created by an errors are not tested here. +func TestSimulateMsgHarvest(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // setup epoch days to 1 + params := app.FarmingKeeper.GetParams(ctx) + params.EpochDays = 1 + app.FarmingKeeper.SetParams(ctx, params) + + // set fixed amountplan + plan := types.NewFixedAmountPlan( + types.NewBasePlan( + 1, + "simulation", + types.PlanTypePrivate, + accounts[0].Address.String(), + accounts[0].Address.String(), + sdk.NewDecCoins( + sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% + ), + mustParseRFC3339("2021-08-01T00:00:00Z"), + mustParseRFC3339("2021-08-31T00:00:00Z"), + ), + sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), + ) + app.FarmingKeeper.SetPlan(ctx, plan) + + // set staking and the amount must be greater than the randomized value range for unharvest + amount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 100_000_000, 1000_000_000)))) + app.FarmingKeeper.Stake(ctx, accounts[0].Address, amount) + app.FarmingKeeper.ProcessQueuedCoins(ctx) + + ctx = ctx.WithBlockTime(mustParseRFC3339("2021-08-20T00:00:00Z")) + err := app.FarmingKeeper.DistributeRewards(ctx) + require.NoError(t, err) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgHarvest(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgHarvest + err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + require.NoError(t, err) + + require.True(t, operationMsg.OK) + require.Equal(t, types.TypeMsgHarvest, msg.Type()) + require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) + require.Equal(t, []string{"stake"}, msg.StakingCoinDenoms) require.Len(t, futureOperations, 0) } @@ -101,7 +248,7 @@ func createTestApp(isCheckTx bool) (*farmingapp.FarmingApp, sdk.Context) { func getTestingAccounts(t *testing.T, r *rand.Rand, app *farmingapp.FarmingApp, ctx sdk.Context, n int) []simtypes.Account { accounts := simtypes.RandomAccounts(r, n) - initAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200) + initAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 100_000_000_000) initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) // add coins to the accounts @@ -114,3 +261,11 @@ func getTestingAccounts(t *testing.T, r *rand.Rand, app *farmingapp.FarmingApp, return accounts } + +func mustParseRFC3339(s string) time.Time { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(err) + } + return t +} diff --git a/x/farming/types/codec.go b/x/farming/types/codec.go index 1e8a175f..58439c78 100644 --- a/x/farming/types/codec.go +++ b/x/farming/types/codec.go @@ -1,23 +1,21 @@ package types import ( - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) -// RegisterLegacyAminoCodec registers the necessary x/farming interfaces and concrete types -// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. -func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - cdc.RegisterConcrete(&MsgCreateFixedAmountPlan{}, "farming/MsgCreateFixedAmountPlan", nil) - cdc.RegisterConcrete(&MsgCreateRatioPlan{}, "farming/MsgCreateRatioPlan", nil) - cdc.RegisterConcrete(&MsgStake{}, "farming/MsgStake", nil) - cdc.RegisterConcrete(&MsgUnstake{}, "farming/MsgUnstake", nil) - cdc.RegisterConcrete(&MsgHarvest{}, "farming/MsgHarvest", nil) -} +// // RegisterLegacyAminoCodec registers the necessary x/farming interfaces and concrete types +// // on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +// func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { +// cdc.RegisterConcrete(&MsgCreateFixedAmountPlan{}, "farming/MsgCreateFixedAmountPlan", nil) +// cdc.RegisterConcrete(&MsgCreateRatioPlan{}, "farming/MsgCreateRatioPlan", nil) +// cdc.RegisterConcrete(&MsgStake{}, "farming/MsgStake", nil) +// cdc.RegisterConcrete(&MsgUnstake{}, "farming/MsgUnstake", nil) +// cdc.RegisterConcrete(&MsgHarvest{}, "farming/MsgHarvest", nil) +// } // RegisterInterfaces registers the x/farming interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { @@ -45,20 +43,20 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } -var ( - amino = codec.NewLegacyAmino() - - // ModuleCdc references the global x/farming module codec. Note, the codec - // should ONLY be used in certain instances of tests and for JSON encoding as Amino - // is still used for that purpose. - // - // The actual codec used for serialization should be provided to x/farming and - // defined at the application level. - ModuleCdc = codec.NewAminoCodec(amino) -) - -func init() { - RegisterLegacyAminoCodec(amino) - cryptocodec.RegisterCrypto(amino) - amino.Seal() -} +// var ( +// amino = codec.NewLegacyAmino() + +// // ModuleCdc references the global x/farming module codec. Note, the codec +// // should ONLY be used in certain instances of tests and for JSON encoding as Amino +// // is still used for that purpose. +// // +// // The actual codec used for serialization should be provided to x/farming and +// // defined at the application level. +// ModuleCdc = codec.NewAminoCodec(amino) +// ) + +// func init() { +// RegisterLegacyAminoCodec(amino) +// cryptocodec.RegisterCrypto(amino) +// amino.Seal() +// } From 34ec306883abdffab4f41b1b5879fe83bec5d4a3 Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 15:07:43 +0900 Subject: [PATCH 07/31] fix: lint checks --- app/sim_test.go | 2 +- x/farming/simulation/operations.go | 4 +++- x/farming/simulation/operations_test.go | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 66baec60..36e99efd 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -57,7 +57,7 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. func interBlockCacheOpt() func(*baseapp.BaseApp) { - return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) // nolint:golint } func TestFullAppSimulation(t *testing.T) { diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index ad600a4e..7926653c 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -297,7 +297,9 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke if len(rewards) == 0 { ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1)) k.ProcessQueuedCoins(ctx) - k.DistributeRewards(ctx) + if err := k.DistributeRewards(ctx); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to distribute rewards"), nil, err + } k.SetLastEpochTime(ctx, ctx.BlockTime()) return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "no rewards to harvest"), nil, nil diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 7d520937..44e53c2c 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -209,11 +209,12 @@ func TestSimulateMsgHarvest(t *testing.T) { // set staking and the amount must be greater than the randomized value range for unharvest amount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 100_000_000, 1000_000_000)))) - app.FarmingKeeper.Stake(ctx, accounts[0].Address, amount) - app.FarmingKeeper.ProcessQueuedCoins(ctx) + _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, amount) + require.NoError(t, err) + app.FarmingKeeper.ProcessQueuedCoins(ctx) ctx = ctx.WithBlockTime(mustParseRFC3339("2021-08-20T00:00:00Z")) - err := app.FarmingKeeper.DistributeRewards(ctx) + err = app.FarmingKeeper.DistributeRewards(ctx) require.NoError(t, err) // begin a new block From 29f67d652a913787f5d41ede4b2e85becf1b577f Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 15:09:22 +0900 Subject: [PATCH 08/31] fix: nolint check for interBlockCacheOpt --- app/sim_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 36e99efd..4e15b5a1 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -56,8 +56,8 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. -func interBlockCacheOpt() func(*baseapp.BaseApp) { - return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) // nolint:golint +func interBlockCacheOpt() func(*baseapp.BaseApp) { // nolint:golint + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) } func TestFullAppSimulation(t *testing.T) { From 8f033607a3e50c8160e41065126bc4e0c0e7a2f2 Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 15:14:59 +0900 Subject: [PATCH 09/31] test: nolint unused code --- app/sim_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sim_test.go b/app/sim_test.go index 4e15b5a1..377f24a3 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -56,7 +56,7 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. -func interBlockCacheOpt() func(*baseapp.BaseApp) { // nolint:golint +func interBlockCacheOpt() func(*baseapp.BaseApp) { // nolint:unused return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) } From d086a899e8bd148b8fc79ab6726bdfcaaa8dd260 Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 15:21:25 +0900 Subject: [PATCH 10/31] lint: last test --- app/sim_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/sim_test.go b/app/sim_test.go index 377f24a3..ae0adb44 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -56,7 +56,8 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. -func interBlockCacheOpt() func(*baseapp.BaseApp) { // nolint:unused +// nolint:deadcode,unused,varcheck +func interBlockCacheOpt() func(*baseapp.BaseApp) { return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) } From 81977c3b685698a84bfc4b829a4986e65d25524c Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 20 Aug 2021 23:01:40 +0900 Subject: [PATCH 11/31] chore: fix broken test and add todo for public plan proposal --- app/app.go | 2 +- app/params/weights.go | 2 +- x/farming/module.go | 15 +++++------ x/farming/simulation/operations_test.go | 4 +-- x/farming/simulation/proposals.go | 34 +++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 x/farming/simulation/proposals.go diff --git a/app/app.go b/app/app.go index dbe5120e..c0912799 100644 --- a/app/app.go +++ b/app/app.go @@ -343,8 +343,8 @@ func NewFarmingApp( upgrade.NewAppModule(app.UpgradeKeeper), evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), - authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), farming.NewAppModule(appCodec, app.FarmingKeeper, app.AccountKeeper, app.BankKeeper, app.DistrKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), // todo: farming ordering ) diff --git a/app/params/weights.go b/app/params/weights.go index 14815ab0..7e1e1505 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -1,7 +1,7 @@ package params const ( - // TODO: farming determine the weights + // farming module simulation operation weights for messages DefaultWeightMsgCreateFixedAmountPlan int = 10 DefaultWeightMsgCreateRatioPlan int = 10 DefaultWeightMsgStake int = 85 diff --git a/x/farming/module.go b/x/farming/module.go index 76ec65de..a4d65d27 100644 --- a/x/farming/module.go +++ b/x/farming/module.go @@ -22,7 +22,6 @@ import ( //"github.com/tendermint/farming/x/farming/client/rest" "github.com/tendermint/farming/x/farming/client/cli" "github.com/tendermint/farming/x/farming/keeper" - "github.com/tendermint/farming/x/farming/simulation" "github.com/tendermint/farming/x/farming/types" ) @@ -187,6 +186,12 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenState(simState) } +// ProposalContents returns all the farming content functions used to +// simulate governance proposals. +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + return simulation.ProposalContents(am.keeper) +} + // RandomizedParams creates randomized farming param changes for the simulator. func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { return simulation.ParamChanges(r) @@ -197,14 +202,6 @@ func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } -// ProposalContents returns all the farming content functions used to -// simulate governance proposals. -func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { - // TODO: implement - // return simulation.ProposalContents(am.keeper) - return nil -} - // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { return simulation.WeightedOperations( diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 44e53c2c..6202377b 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -36,7 +36,7 @@ func TestWeightedOperations(t *testing.T) { s := rand.NewSource(1) r := rand.New(s) - accs := simtypes.RandomAccounts(r, 3) + accs := getTestingAccounts(t, r, app, ctx, 1) expected := []struct { weight int @@ -237,7 +237,7 @@ func TestSimulateMsgHarvest(t *testing.T) { } func createTestApp(isCheckTx bool) (*farmingapp.FarmingApp, sdk.Context) { - app := farmingapp.Setup(false) + app := farmingapp.Setup(isCheckTx) ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}) app.MintKeeper.SetParams(ctx, minttypes.DefaultParams()) diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go new file mode 100644 index 00000000..f64260a1 --- /dev/null +++ b/x/farming/simulation/proposals.go @@ -0,0 +1,34 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/tendermint/farming/x/farming/keeper" +) + +// OpWeightSubmitCommunitySpendProposal app params key for community spend proposal +const OpWeightSubmitCommunitySpendProposal = "op_weight_submit_community_spend_proposal" + +// ProposalContents defines the module weighted proposals' contents +func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { + // TODO: not implemented yet + return nil + // return []simtypes.WeightedProposalContent{ + // simulation.NewWeightedProposalContent( + // OpWeightSubmitCommunitySpendProposal, + // simappparams.DefaultWeightCommunitySpendProposal, + // SimulateCommunityPoolSpendProposalContent(k), + // ), + // } +} + +// SimulateCommunityPoolSpendProposalContent generates random community-pool-spend proposal content +func SimulateCommunityPoolSpendProposalContent(k keeper.Keeper) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + // TODO: not implemented yet + return nil + } +} From 8138dfbc42ad197bcf449e5f1fc8594b8c04af00 Mon Sep 17 00:00:00 2001 From: kogisin Date: Sat, 21 Aug 2021 13:24:06 +0900 Subject: [PATCH 12/31] chore: fix param changes, randomize epoch days, add farming fee collector, add workflow --- .github/workflows/test.yml | 13 +++++++++++ app/app.go | 2 +- x/farming/module.go | 4 ++-- x/farming/simulation/genesis.go | 15 ++++++++++++- x/farming/simulation/genesis_test.go | 2 ++ x/farming/simulation/operations.go | 32 +++++++++++----------------- x/farming/simulation/params.go | 19 ++++++++++++++--- x/farming/simulation/params_test.go | 9 ++++---- x/farming/simulation/proposals.go | 12 +++++------ 9 files changed, 72 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5e69b94..b84ce634 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,3 +88,16 @@ jobs: - uses: codecov/codecov-action@v1.0.14 with: file: ./coverage.txt + + test-simulation: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2.1.3 + with: + go-version: 1.15 + - name: Display go version + run: go version + - name: Testing simulation + run: make test-sim-nondeterminism diff --git a/app/app.go b/app/app.go index c0912799..dbe5120e 100644 --- a/app/app.go +++ b/app/app.go @@ -343,8 +343,8 @@ func NewFarmingApp( upgrade.NewAppModule(app.UpgradeKeeper), evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), - farming.NewAppModule(appCodec, app.FarmingKeeper, app.AccountKeeper, app.BankKeeper, app.DistrKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + farming.NewAppModule(appCodec, app.FarmingKeeper, app.AccountKeeper, app.BankKeeper, app.DistrKeeper), // todo: farming ordering ) diff --git a/x/farming/module.go b/x/farming/module.go index a4d65d27..4c00a786 100644 --- a/x/farming/module.go +++ b/x/farming/module.go @@ -44,7 +44,7 @@ func (AppModuleBasic) Name() string { // RegisterLegacyAminoCodec registers the farming module's types for the given codec. func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - //types.RegisterLegacyAminoCodec(cdc) + // types.RegisterLegacyAminoCodec(cdc) } // DefaultGenesis returns default genesis state as raw bytes for the farming @@ -197,7 +197,7 @@ func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { return simulation.ParamChanges(r) } -// RegisterStoreDecoder registers a decoder for farming module's types +// RegisterStoreDecoder registers a decoder for farming module's types. func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } diff --git a/x/farming/simulation/genesis.go b/x/farming/simulation/genesis.go index f5c95606..8fabfc5c 100644 --- a/x/farming/simulation/genesis.go +++ b/x/farming/simulation/genesis.go @@ -19,6 +19,7 @@ const ( PrivatePlanCreationFee = "private_plan_creation_fee" StakingCreationFee = "staking_creation_fee" EpochDays = "epoch_days" + FarmingFeeCollector = "farming_fee_collector" ) // GenPrivatePlanCreationFee return randomized private plan creation fee. @@ -33,7 +34,12 @@ func GenStakingCreationFee(r *rand.Rand) sdk.Coins { // GenEpochDays return default EpochDays. func GenEpochDays(r *rand.Rand) uint32 { - return types.DefaultEpochDays + return uint32(simulation.RandIntBetween(r, int(types.DefaultEpochDays), 10)) +} + +// GenFarmingFeeCollector returns default farming fee collector. +func GenFarmingFeeCollector(r *rand.Rand) string { + return types.DefaultFarmingFeeCollector } // RandomizedGenState generates a random GenesisState for farming. @@ -56,11 +62,18 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { epochDays = GenEpochDays(r) }, ) + var feeCollector string + simState.AppParams.GetOrGenerate( + simState.Cdc, FarmingFeeCollector, &feeCollector, simState.Rand, + func(r *rand.Rand) { feeCollector = GenFarmingFeeCollector(r) }, + ) + farmingGenesis := types.GenesisState{ Params: types.Params{ PrivatePlanCreationFee: privatePlanCreationFee, StakingCreationFee: stakingCreationFee, EpochDays: epochDays, + FarmingFeeCollector: feeCollector, }, } diff --git a/x/farming/simulation/genesis_test.go b/x/farming/simulation/genesis_test.go index 3760ed2b..50c65da3 100644 --- a/x/farming/simulation/genesis_test.go +++ b/x/farming/simulation/genesis_test.go @@ -43,10 +43,12 @@ func TestRandomizedGenState(t *testing.T) { dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(336122540))) dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(208240456))) dec3 := uint32(1) + dec4 := "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" require.Equal(t, dec1, genState.Params.PrivatePlanCreationFee) require.Equal(t, dec2, genState.Params.StakingCreationFee) require.Equal(t, dec3, genState.Params.EpochDays) + require.Equal(t, dec4, genState.Params.FarmingFeeCollector) } // TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 7926653c..bd7fb164 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -1,7 +1,6 @@ package simulation import ( - "fmt" "math/rand" "time" @@ -103,10 +102,9 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe spendable := bk.SpendableCoins(ctx, account.GetAddress()) params := k.GetParams(ctx) - coins, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + _, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) if hasNeg { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "lower balance"), - nil, fmt.Errorf("spendable %s is lower than plan creation fee %s", spendable.String(), coins.String()) + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "insufficient balance for plan creation fee"), nil, nil } name := "simulation" @@ -151,10 +149,9 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k spendable := bk.SpendableCoins(ctx, account.GetAddress()) params := k.GetParams(ctx) - coins, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + _, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) if hasNeg { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateRatioPlan, "lower balance"), - nil, fmt.Errorf("spendable %s is lower than plan creation fee %s", spendable.String(), coins.String()) + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateRatioPlan, "insufficient balance for plan creation fee"), nil, nil } name := "simulation" @@ -196,18 +193,17 @@ func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep account := ak.GetAccount(ctx, simAccount.Address) spendable := bk.SpendableCoins(ctx, account.GetAddress()) - params := k.GetParams(ctx) - coins, hasNeg := spendable.SafeSub(params.StakingCreationFee) - if hasNeg { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgStake, "lower balance"), - nil, fmt.Errorf("spendable %s is lower than staking creation fee %s", spendable.String(), coins.String()) - } - farmer := account.GetAddress() stakingCoins := sdk.NewCoins( sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 100_000_000))), ) + params := k.GetParams(ctx) + _, hasNeg := spendable.SafeSub(params.StakingCreationFee.Add(stakingCoins...)) + if hasNeg { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgStake, "insufficient balance for staking creation fee"), nil, nil + } + msg := types.NewMsgStake(farmer, stakingCoins) txCtx := simulation.OperationInput{ @@ -248,13 +244,11 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke // staking must exist in order to unharvest staking, found := k.GetStakingByFarmer(ctx, farmer) if !found { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), - nil, fmt.Errorf("staking by %s not found", farmer) + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), nil, nil } if !staking.StakedCoins.Add(staking.QueuedCoins...).IsAllGTE(unstakingCoins) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), - nil, fmt.Errorf("%s is smaller than %s", staking.StakedCoins.Add(staking.QueuedCoins...).String(), unstakingCoins.String()) + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), nil, nil } msg := types.NewMsgUnstake(farmer, unstakingCoins) @@ -298,7 +292,7 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1)) k.ProcessQueuedCoins(ctx) if err := k.DistributeRewards(ctx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to distribute rewards"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to distribute rewards"), nil, nil } k.SetLastEpochTime(ctx, ctx.BlockTime()) diff --git a/x/farming/simulation/params.go b/x/farming/simulation/params.go index e3dd0bdf..213c0cb3 100644 --- a/x/farming/simulation/params.go +++ b/x/farming/simulation/params.go @@ -18,17 +18,30 @@ func ParamChanges(r *rand.Rand) []simtypes.ParamChange { return []simtypes.ParamChange{ simulation.NewSimParamChange(types.ModuleName, string(types.KeyPrivatePlanCreationFee), func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", GenPrivatePlanCreationFee(r)) + bz, err := GenPrivatePlanCreationFee(r).MarshalJSON() + if err != nil { + panic(err) + } + return string(bz) }, ), simulation.NewSimParamChange(types.ModuleName, string(types.KeyStakingCreationFee), func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", GenStakingCreationFee(r)) + bz, err := GenPrivatePlanCreationFee(r).MarshalJSON() + if err != nil { + panic(err) + } + return string(bz) }, ), simulation.NewSimParamChange(types.ModuleName, string(types.KeyEpochDays), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenEpochDays(r)) + return fmt.Sprintf("%d", GenEpochDays(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyFarmingFeeCollector), + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", GenFarmingFeeCollector(r)) }, ), } diff --git a/x/farming/simulation/params_test.go b/x/farming/simulation/params_test.go index 4e45602e..a6d4182b 100644 --- a/x/farming/simulation/params_test.go +++ b/x/farming/simulation/params_test.go @@ -19,13 +19,14 @@ func TestParamChanges(t *testing.T) { simValue string subspace string }{ - {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "\"298498081stake\"", "farming"}, - {"farming/StakingCreationFee", "StakingCreationFee", "\"427131847stake\"", "farming"}, - {"farming/EpochDays", "EpochDays", "\"1\"", "farming"}, + {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "[{\"denom\":\"stake\",\"amount\":\"298498081\"}]", "farming"}, + {"farming/StakingCreationFee", "StakingCreationFee", "[{\"denom\":\"stake\",\"amount\":\"427131847\"}]", "farming"}, + {"farming/EpochDays", "EpochDays", "3", "farming"}, + {"farming/FarmingFeeCollector", "FarmingFeeCollector", "\"cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x\"", "farming"}, } paramChanges := simulation.ParamChanges(r) - require.Len(t, paramChanges, 3) + require.Len(t, paramChanges, 4) for i, p := range paramChanges { require.Equal(t, expected[i].composedKey, p.ComposedKey()) diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go index f64260a1..5458ecdd 100644 --- a/x/farming/simulation/proposals.go +++ b/x/farming/simulation/proposals.go @@ -9,8 +9,8 @@ import ( "github.com/tendermint/farming/x/farming/keeper" ) -// OpWeightSubmitCommunitySpendProposal app params key for community spend proposal -const OpWeightSubmitCommunitySpendProposal = "op_weight_submit_community_spend_proposal" +// OpWeightSubmitPublicPlanProposals app params key for public plan proposals +const OpWeightSubmitPublicPlanProposals = "op_weight_submit_public_plan_proposals" // ProposalContents defines the module weighted proposals' contents func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { @@ -18,15 +18,15 @@ func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { return nil // return []simtypes.WeightedProposalContent{ // simulation.NewWeightedProposalContent( - // OpWeightSubmitCommunitySpendProposal, - // simappparams.DefaultWeightCommunitySpendProposal, + // OpWeightSubmitPublicPlanProposals, + // simappparams.DefaultWeightPublicPlanProposals, // SimulateCommunityPoolSpendProposalContent(k), // ), // } } -// SimulateCommunityPoolSpendProposalContent generates random community-pool-spend proposal content -func SimulateCommunityPoolSpendProposalContent(k keeper.Keeper) simtypes.ContentSimulatorFn { +// SimulatePublicPlanProposalsProposalContent generates random public plan proposals proposal content +func SimulatePublicPlanProposalsProposalContent(k keeper.Keeper) simtypes.ContentSimulatorFn { return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { // TODO: not implemented yet return nil From 9d0730088f9a2f9669c4cc6f33e032b6f071793c Mon Sep 17 00:00:00 2001 From: kogisin Date: Sat, 21 Aug 2021 13:31:19 +0900 Subject: [PATCH 13/31] fix: broken test --- x/farming/simulation/genesis_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/farming/simulation/genesis_test.go b/x/farming/simulation/genesis_test.go index 50c65da3..62688f36 100644 --- a/x/farming/simulation/genesis_test.go +++ b/x/farming/simulation/genesis_test.go @@ -42,7 +42,7 @@ func TestRandomizedGenState(t *testing.T) { dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(336122540))) dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(208240456))) - dec3 := uint32(1) + dec3 := uint32(7) dec4 := "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" require.Equal(t, dec1, genState.Params.PrivatePlanCreationFee) From 5221ea82d8fb49458b320214114ed9437352242f Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 3 Sep 2021 11:47:44 +0900 Subject: [PATCH 14/31] docs: fix backticks location --- docs/How-To/client.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/How-To/client.md b/docs/How-To/client.md index 992ef2c2..8e6a0c6d 100644 --- a/docs/How-To/client.md +++ b/docs/How-To/client.md @@ -110,7 +110,6 @@ $BINARY start } ] } -``` # Send to create a private fixed amount plan farmingd tx farming create-private-fixed-plan private-fixed-plan.json \ @@ -119,6 +118,7 @@ farmingd tx farming create-private-fixed-plan private-fixed-plan.json \ --keyring-backend test \ --broadcast-mode block \ --yes +``` ```json { From 2d25621256c8a096d0ef02665ae68f4ada7bf6fa Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 3 Sep 2021 13:04:32 +0900 Subject: [PATCH 15/31] chore: add migration tests, sort imports, fix db to newDB, update Makefile --- Makefile | 13 ++- app/app_test.go | 239 +++++++++++++++++++++++++++++++++++++++++++++++- app/sim_test.go | 8 +- go.mod | 1 + 4 files changed, 254 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 90deed22..929fa2c5 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,18 @@ test-sim-nondeterminism: @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \ -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h -.PHONY: test-sim-nondeterminism +test-sim-import-export: runsim + @echo "Running application import/export simulation. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 10 5 TestAppImportExport + +test-sim-after-import: runsim + @echo "Running application simulation-after-import. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 10 5 TestAppSimulationAfterImport + +.PHONY: \ +test-sim-nondeterminism \ +test-sim-import-export \ +test-sim-after-import ############################################################################### ### Localnet ### diff --git a/app/app_test.go b/app/app_test.go index 082cef80..7a457598 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -5,17 +5,51 @@ import ( "os" "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/tests/mocks" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/capability" + "github.com/cosmos/cosmos-sdk/x/crisis" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/evidence" + feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/upgrade" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + + "github.com/tendermint/farming/x/farming" ) -func TestSimAppExport(t *testing.T) { +func TestSimAppExportAndBlockedAddrs(t *testing.T) { encCfg := MakeTestEncodingConfig() db := dbm.NewMemDB() app := NewFarmingApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) + for acc := range maccPerms { + require.True( + t, + app.BankKeeper.BlockedAddr(app.AccountKeeper.GetModuleAddress(acc)), + "ensure that blocked addresses are properly set in bank keeper", + ) + } + genesisState := NewDefaultGenesisState(encCfg.Marshaler) stateBytes, err := json.MarshalIndent(genesisState, "", " ") require.NoError(t, err) @@ -29,8 +63,209 @@ func TestSimAppExport(t *testing.T) { ) app.Commit() - res, err := app.ExportAppStateAndValidators(false, []string{}) + // Making a new app object with the db, so that initchain hasn't been called + app2 := NewFarmingApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) + res, err := app2.ExportAppStateAndValidators(false, []string{}) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") + _, err = res.AppState.MarshalJSON() require.NoError(t, err) } + +func TestGetMaccPerms(t *testing.T) { + dup := GetMaccPerms() + require.Equal(t, maccPerms, dup, "duplicated module account permissions differed from actual module account permissions") +} + +func TestRunMigrations(t *testing.T) { + db := dbm.NewMemDB() + encCfg := MakeTestEncodingConfig() + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) + + // Create a new baseapp and configurator for the purpose of this test. + bApp := baseapp.NewBaseApp(appName, logger, db, encCfg.TxConfig.TxDecoder()) + bApp.SetCommitMultiStoreTracer(nil) + bApp.SetInterfaceRegistry(encCfg.InterfaceRegistry) + app.BaseApp = bApp + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + + // We register all modules on the Configurator, except x/bank. x/bank will + // serve as the test subject on which we run the migration tests. + // + // The loop below is the same as calling `RegisterServices` on + // ModuleManager, except that we skip x/bank. + for _, module := range app.mm.Modules { + if module.Name() == banktypes.ModuleName { + continue + } + + module.RegisterServices(app.configurator) + } + + // Initialize the chain + app.InitChain(abci.RequestInitChain{}) + app.Commit() + + testCases := []struct { + name string + moduleName string + forVersion uint64 + expRegErr bool // errors while registering migration + expRegErrMsg string + expRunErr bool // errors while running migration + expRunErrMsg string + expCalled int + }{ + { + "cannot register migration for version 0", + "bank", 0, + true, "module migration versions should start at 1: invalid version", false, "", 0, + }, + { + "throws error on RunMigrations if no migration registered for bank", + "", 1, + false, "", true, "no migrations found for module bank: not found", 0, + }, + { + "can register and run migration handler for x/bank", + "bank", 1, + false, "", false, "", 1, + }, + { + "cannot register migration handler for same module & forVersion", + "bank", 1, + true, "another migration for module bank and version 1 already exists: internal logic error", false, "", 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var err error + + // Since it's very hard to test actual in-place store migrations in + // tests (due to the difficulty of maintaining multiple versions of a + // module), we're just testing here that the migration logic is + // called. + called := 0 + + if tc.moduleName != "" { + // Register migration for module from version `forVersion` to `forVersion+1`. + err = app.configurator.RegisterMigration(tc.moduleName, tc.forVersion, func(sdk.Context) error { + called++ + + return nil + }) + + if tc.expRegErr { + require.EqualError(t, err, tc.expRegErrMsg) + + return + } + } + require.NoError(t, err) + + // Run migrations only for bank. That's why we put the initial + // version for bank as 1, and for all other modules, we put as + // their latest ConsensusVersion. + _, err = app.mm.RunMigrations( + app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}), app.configurator, + module.VersionMap{ + "bank": 1, + "auth": auth.AppModule{}.ConsensusVersion(), + "authz": authzmodule.AppModule{}.ConsensusVersion(), + "staking": staking.AppModule{}.ConsensusVersion(), + "mint": mint.AppModule{}.ConsensusVersion(), + "distribution": distribution.AppModule{}.ConsensusVersion(), + "slashing": slashing.AppModule{}.ConsensusVersion(), + "gov": gov.AppModule{}.ConsensusVersion(), + "params": params.AppModule{}.ConsensusVersion(), + "upgrade": upgrade.AppModule{}.ConsensusVersion(), + "vesting": vesting.AppModule{}.ConsensusVersion(), + "feegrant": feegrantmodule.AppModule{}.ConsensusVersion(), + "evidence": evidence.AppModule{}.ConsensusVersion(), + "crisis": crisis.AppModule{}.ConsensusVersion(), + "genutil": genutil.AppModule{}.ConsensusVersion(), + "capability": capability.AppModule{}.ConsensusVersion(), + "farming": farming.AppModule{}.ConsensusVersion(), + }, + ) + if tc.expRunErr { + require.EqualError(t, err, tc.expRunErrMsg) + } else { + require.NoError(t, err) + // Make sure bank's migration is called. + require.Equal(t, tc.expCalled, called) + } + }) + } +} + +func TestInitGenesisOnMigration(t *testing.T) { + db := dbm.NewMemDB() + encCfg := MakeTestEncodingConfig() + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + app := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) + ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + + // Create a mock module. This module will serve as the new module we're + // adding during a migration. + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockModule := mocks.NewMockAppModule(mockCtrl) + mockDefaultGenesis := json.RawMessage(`{"key": "value"}`) + mockModule.EXPECT().DefaultGenesis(gomock.Eq(app.appCodec)).Times(1).Return(mockDefaultGenesis) + mockModule.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(app.appCodec), gomock.Eq(mockDefaultGenesis)).Times(1).Return(nil) + mockModule.EXPECT().ConsensusVersion().Times(1).Return(uint64(0)) + + app.mm.Modules["mock"] = mockModule + + // Run migrations only for "mock" module. We exclude it from + // the VersionMap to simulate upgrading with a new module. + _, err := app.mm.RunMigrations(ctx, app.configurator, + module.VersionMap{ + "bank": bank.AppModule{}.ConsensusVersion(), + "auth": auth.AppModule{}.ConsensusVersion(), + "authz": authzmodule.AppModule{}.ConsensusVersion(), + "staking": staking.AppModule{}.ConsensusVersion(), + "mint": mint.AppModule{}.ConsensusVersion(), + "distribution": distribution.AppModule{}.ConsensusVersion(), + "slashing": slashing.AppModule{}.ConsensusVersion(), + "gov": gov.AppModule{}.ConsensusVersion(), + "params": params.AppModule{}.ConsensusVersion(), + "upgrade": upgrade.AppModule{}.ConsensusVersion(), + "vesting": vesting.AppModule{}.ConsensusVersion(), + "feegrant": feegrantmodule.AppModule{}.ConsensusVersion(), + "evidence": evidence.AppModule{}.ConsensusVersion(), + "crisis": crisis.AppModule{}.ConsensusVersion(), + "genutil": genutil.AppModule{}.ConsensusVersion(), + "capability": capability.AppModule{}.ConsensusVersion(), + "farming": farming.AppModule{}.ConsensusVersion(), + }, + ) + require.NoError(t, err) +} + +func TestUpgradeStateOnGenesis(t *testing.T) { + encCfg := MakeTestEncodingConfig() + db := dbm.NewMemDB() + app := NewFarmingApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, EmptyAppOptions{}) + genesisState := NewDefaultGenesisState(encCfg.Marshaler) + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // Initialize the chain + app.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + + // make sure the upgrade keeper has version map in state + ctx := app.NewContext(false, tmproto.Header{}) + vm := app.UpgradeKeeper.GetModuleVersionMap(ctx) + for v, i := range app.mm.Modules { + require.Equal(t, vm[v], i.ConsensusVersion()) + } +} diff --git a/app/sim_test.go b/app/sim_test.go index ae0adb44..79ea2ea5 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -29,12 +29,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/simulation" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - farmingtypes "github.com/tendermint/farming/x/farming/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + + farmingtypes "github.com/tendermint/farming/x/farming/types" ) // Get flags every time the simulator is run @@ -151,7 +151,7 @@ func TestAppImportExport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + newApp := NewFarmingApp(logger, newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) require.Equal(t, appName, app.Name()) var genesisState GenesisState @@ -253,7 +253,7 @@ func TestAppSimulationAfterImport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewFarmingApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + newApp := NewFarmingApp(logger, newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) require.Equal(t, appName, app.Name()) newApp.InitChain(abci.RequestInitChain{ diff --git a/go.mod b/go.mod index ced181dc..1aa24910 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/cosmos/cosmos-sdk v0.43.0 github.com/gogo/protobuf v1.3.3 + github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 From a7d3e70185cb88af6197820736f09863f413d01f Mon Sep 17 00:00:00 2001 From: kogisin Date: Mon, 6 Sep 2021 17:46:23 +0900 Subject: [PATCH 16/31] chore: fix simulation logic, add minter permission to module account, work in progress to solve harvest issue --- app/app.go | 11 +- app/params/weights.go | 2 +- x/farming/simulation/decoder.go | 16 +- x/farming/simulation/decoder_test.go | 46 +++++- x/farming/simulation/genesis.go | 6 +- x/farming/simulation/operations.go | 105 +++++++++++--- x/farming/simulation/operations_test.go | 185 +++++++++++++++++++----- 7 files changed, 294 insertions(+), 77 deletions(-) diff --git a/app/app.go b/app/app.go index dbe5120e..afc1237c 100644 --- a/app/app.go +++ b/app/app.go @@ -137,7 +137,7 @@ var ( stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, govtypes.ModuleName: {authtypes.Burner}, - farmingtypes.ModuleName: nil, + farmingtypes.ModuleName: {authtypes.Minter}, // todo: farming Staking Reserve Coin TBD } ) @@ -580,6 +580,15 @@ func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) { rtr.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/", staticServer)) } +// GetMaccPerms returns a copy of the module account permissions +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + for k, v := range maccPerms { + dupMaccPerms[k] = v + } + return dupMaccPerms +} + // initParamsKeeper init params keeper and its subspaces func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) diff --git a/app/params/weights.go b/app/params/weights.go index 7e1e1505..30c44cd9 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -5,6 +5,6 @@ const ( DefaultWeightMsgCreateFixedAmountPlan int = 10 DefaultWeightMsgCreateRatioPlan int = 10 DefaultWeightMsgStake int = 85 - DefaultWeightMsgUnstake int = 50 + DefaultWeightMsgUnstake int = 30 DefaultWeightMsgHarvest int = 30 ) diff --git a/x/farming/simulation/decoder.go b/x/farming/simulation/decoder.go index 4920906c..5ba215f5 100644 --- a/x/farming/simulation/decoder.go +++ b/x/farming/simulation/decoder.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" "github.com/tendermint/farming/x/farming/types" @@ -15,27 +16,28 @@ import ( func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { return func(kvA, kvB kv.Pair) string { switch { - case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix), - bytes.Equal(kvA.Key[:1], types.PlansByFarmerIndexKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix): var pA, pB types.BasePlan cdc.MustUnmarshal(kvA.Value, &pA) cdc.MustUnmarshal(kvA.Value, &pB) return fmt.Sprintf("%v\n%v", pA, pB) - case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix), - bytes.Equal(kvA.Key[:1], types.StakingByFarmerIndexKeyPrefix), - bytes.Equal(kvA.Key[:1], types.StakingsByStakingCoinDenomIndexKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix): var sA, sB types.Staking cdc.MustUnmarshal(kvA.Value, &sA) cdc.MustUnmarshal(kvA.Value, &sB) return fmt.Sprintf("%v\n%v", sA, sB) - case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix), - bytes.Equal(kvA.Key[:1], types.RewardsByFarmerIndexKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix): var rA, rB types.Reward cdc.MustUnmarshal(kvA.Value, &rA) return fmt.Sprintf("%v\n%v", rA, rB) + case bytes.Equal(kvA.Key[:1], types.PlansByFarmerIndexKeyPrefix), + bytes.Equal(kvA.Key[:1], types.StakingByFarmerIndexKeyPrefix), + bytes.Equal(kvA.Key[:1], types.RewardsByFarmerIndexKeyPrefix): + return fmt.Sprintf("%v\n%v", sdk.AccAddress(kvA.Value), sdk.AccAddress(kvB.Value)) + default: panic(fmt.Sprintf("invalid farming key prefix %X", kvA.Key[:1])) } diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index fddac2fe..32619f3a 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -6,13 +6,45 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" "github.com/tendermint/farming/x/farming/simulation" "github.com/tendermint/farming/x/farming/types" ) +// case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix): +// var pA, pB types.BasePlan +// cdc.MustUnmarshal(kvA.Value, &pA) +// cdc.MustUnmarshal(kvA.Value, &pB) +// return fmt.Sprintf("%v\n%v", pA, pB) + +// case bytes.Equal(kvA.Key[:1], types.PlansByFarmerIndexKeyPrefix), +// bytes.Equal(kvA.Key[:1], types.StakingByFarmerIndexKeyPrefix), +// bytes.Equal(kvA.Key[:1], types.RewardsByFarmerIndexKeyPrefix): +// return fmt.Sprintf("%v\n%v", sdk.AccAddress(kvA.Value), sdk.AccAddress(kvB.Value)) + +// case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix): +// var sA, sB types.Staking +// cdc.MustUnmarshal(kvA.Value, &sA) +// cdc.MustUnmarshal(kvA.Value, &sB) +// return fmt.Sprintf("%v\n%v", sA, sB) + +// case bytes.Equal(kvA.Key[:1], types.StakingsByStakingCoinDenomIndexKeyPrefix): +// return fmt.Sprintf("%s\n%s", kvA.Value, kvB.Value) + +// case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix): +// var rA, rB types.Reward +// cdc.MustUnmarshal(kvA.Value, &rA) +// return fmt.Sprintf("%v\n%v", rA, rB) + +var ( + pk1 = ed25519.GenPrivKey().PubKey() + farmerAddr1 = sdk.AccAddress(pk1.Address()) +) + func TestDecodeFarmingStore(t *testing.T) { cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) @@ -24,12 +56,11 @@ func TestDecodeFarmingStore(t *testing.T) { kvPairs := kv.Pairs{ Pairs: []kv.Pair{ {Key: types.PlanKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, - {Key: types.PlansByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, {Key: types.StakingKeyPrefix, Value: cdc.MustMarshal(&staking)}, - {Key: types.StakingByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&staking)}, - {Key: types.StakingsByStakingCoinDenomIndexKeyPrefix, Value: cdc.MustMarshal(&staking)}, {Key: types.RewardKeyPrefix, Value: cdc.MustMarshal(&reward)}, - {Key: types.RewardsByFarmerIndexKeyPrefix, Value: cdc.MustMarshal(&reward)}, + {Key: types.PlansByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, + {Key: types.StakingByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, + {Key: types.RewardsByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, {Key: []byte{0x99}, Value: []byte{0x99}}, }, } @@ -39,12 +70,11 @@ func TestDecodeFarmingStore(t *testing.T) { expectedLog string }{ {"Plan", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, - {"Plans", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, {"Staking", fmt.Sprintf("%v\n%v", staking, staking)}, - {"StakingByFarmer", fmt.Sprintf("%v\n%v", staking, staking)}, - {"Stakings", fmt.Sprintf("%v\n%v", staking, staking)}, {"Reward", fmt.Sprintf("%v\n%v", reward, reward)}, - {"Rewards", fmt.Sprintf("%v\n%v", reward, reward)}, + {"PlansByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, + {"StakingByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, + {"RewardsByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, {"other", ""}, } for i, tt := range tests { diff --git a/x/farming/simulation/genesis.go b/x/farming/simulation/genesis.go index 8fabfc5c..478c40d3 100644 --- a/x/farming/simulation/genesis.go +++ b/x/farming/simulation/genesis.go @@ -24,15 +24,15 @@ const ( // GenPrivatePlanCreationFee return randomized private plan creation fee. func GenPrivatePlanCreationFee(r *rand.Rand) sdk.Coins { - return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 1_000_000_000)))) + return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 100_000_000)))) } // GenStakingCreationFee return randomized staking creation fee. func GenStakingCreationFee(r *rand.Rand) sdk.Coins { - return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 1_000_000_000)))) + return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 100_000_000)))) } -// GenEpochDays return default EpochDays. +// GenEpochDays return default epoch days. func GenEpochDays(r *rand.Rand) uint32 { return uint32(simulation.RandIntBetween(r, int(types.DefaultEpochDays), 10)) } diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index bd7fb164..1c5e5b61 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -2,7 +2,6 @@ package simulation import ( "math/rand" - "time" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -107,16 +106,28 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "insufficient balance for plan creation fee"), nil, nil } + poolCoins, err := mintPoolCoins(ctx, r, bk, simAccount) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil + } + name := "simulation" creatorAcc := account.GetAddress() stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) - startTime := time.Now().UTC() - endTime := startTime.AddDate(0, 0, 1) + startTime := ctx.BlockTime() + endTime := startTime.AddDate(0, 1, 0) epochAmount := sdk.NewCoins( - sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 1_000_000_000))), + sdk.NewInt64Coin(poolCoins[r.Intn(3)].Denom, int64(simtypes.RandIntBetween(r, 10_000_000, 1_000_000_000))), ) - msg := types.NewMsgCreateFixedAmountPlan(name, creatorAcc, stakingCoinWeights, startTime, endTime, epochAmount) + msg := types.NewMsgCreateFixedAmountPlan( + name, + creatorAcc, + stakingCoinWeights, + startTime, + endTime, + epochAmount, + ) txCtx := simulation.OperationInput{ R: r, @@ -154,14 +165,26 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateRatioPlan, "insufficient balance for plan creation fee"), nil, nil } + _, err := mintPoolCoins(ctx, r, bk, simAccount) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil + } + name := "simulation" creatorAcc := account.GetAddress() stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) - startTime := time.Now().UTC() - endTime := startTime.AddDate(0, 0, 1) + startTime := ctx.BlockTime() + endTime := startTime.AddDate(0, 1, 0) epochRatio := sdk.NewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 10)), 1) - msg := types.NewMsgCreateRatioPlan(name, creatorAcc, stakingCoinWeights, startTime, endTime, epochRatio) + msg := types.NewMsgCreateRatioPlan( + name, + creatorAcc, + stakingCoinWeights, + startTime, + endTime, + epochRatio, + ) txCtx := simulation.OperationInput{ R: r, @@ -195,11 +218,11 @@ func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep farmer := account.GetAddress() stakingCoins := sdk.NewCoins( - sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 100_000_000))), + sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 1_000_000_000))), ) params := k.GetParams(ctx) - _, hasNeg := spendable.SafeSub(params.StakingCreationFee.Add(stakingCoins...)) + _, hasNeg := spendable.SafeSub(stakingCoins.Add(params.StakingCreationFee...)) if hasNeg { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgStake, "insufficient balance for staking creation fee"), nil, nil } @@ -247,10 +270,16 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), nil, nil } + // sum of staked and queued coins must be greater than unstaking coins if !staking.StakedCoins.Add(staking.QueuedCoins...).IsAllGTE(unstakingCoins) { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), nil, nil } + // spendable must be greater than unstaking coins + if !spendable.IsAllGT(unstakingCoins) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), nil, nil + } + msg := types.NewMsgUnstake(farmer, unstakingCoins) txCtx := simulation.OperationInput{ @@ -278,28 +307,36 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount, _ := simtypes.RandomAcc(r, accs) + // TODO: not fully implemented yet. It needs debugging why + // there are no rewards although it processes queued coins and distribute rewards + stakings := k.GetAllStakings(ctx) + if len(stakings) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to find any staking"), nil, nil + } - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) + var simAccount simtypes.Account + simAccount.Address = stakings[r.Intn(len(stakings))].GetFarmer() - farmer := account.GetAddress() - stakingCoinDenoms := []string{sdk.DefaultBondDenom} - - // add a day to increase epoch if there is no harvest rewards - rewards := k.GetRewardsByFarmer(ctx, farmer) - if len(rewards) == 0 { - ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 1)) - k.ProcessQueuedCoins(ctx) + // process queued coins and distribute rewards relative to the current epoch days + params := k.GetParams(ctx) + k.ProcessQueuedCoins(ctx) + ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, int(params.EpochDays))) + for i := 0; i < int(params.EpochDays); i++ { if err := k.DistributeRewards(ctx); err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to distribute rewards"), nil, nil } - k.SetLastEpochTime(ctx, ctx.BlockTime()) + } + k.SetLastEpochTime(ctx, ctx.BlockTime()) + rewards := k.GetRewardsByFarmer(ctx, simAccount.Address) + if len(rewards) == 0 { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "no rewards to harvest"), nil, nil } - msg := types.NewMsgHarvest(farmer, stakingCoinDenoms) + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + msg := types.NewMsgHarvest(simAccount.Address, []string{sdk.DefaultBondDenom}) txCtx := simulation.OperationInput{ R: r, @@ -319,3 +356,25 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return simulation.GenAndDeliverTxWithRandFees(txCtx) } } + +// mintPoolCoins mints random amount of coins with the provided pool coin denoms and +// send them to the simulated account. +func mintPoolCoins(ctx sdk.Context, r *rand.Rand, bk types.BankKeeper, acc simtypes.Account) (mintCoins sdk.Coins, err error) { + for _, denom := range []string{ + "pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", + "pool3036F43CB8131A1A63D2B3D3B11E9CF6FA2A2B6FEC17D5AD283C25C939614A8C", + "poolE4D2617BFE03E1146F6BBA1D9893F2B3D77BA29E7ED532BB721A39FF1ECC1B07", + } { + mintCoins = mintCoins.Add(sdk.NewInt64Coin(denom, int64(simtypes.RandIntBetween(r, 1e14, 1e15)))) + } + + if err := bk.MintCoins(ctx, types.ModuleName, mintCoins); err != nil { + return nil, err + } + + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, acc.Address, mintCoins); err != nil { + return nil, err + } + + return mintCoins, nil +} diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 6202377b..cef74d7d 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -1,21 +1,22 @@ package simulation_test import ( + "fmt" "math/rand" "testing" "time" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" farmingapp "github.com/tendermint/farming/app" - farmingparams "github.com/tendermint/farming/app/params" + "github.com/tendermint/farming/app/params" "github.com/tendermint/farming/x/farming/simulation" "github.com/tendermint/farming/x/farming/types" ) @@ -29,10 +30,7 @@ func TestWeightedOperations(t *testing.T) { cdc := app.AppCodec() appParams := make(simtypes.AppParams) - weightedOps := simulation.WeightedOperations( - appParams, cdc, app.AccountKeeper, - app.BankKeeper, app.FarmingKeeper, - ) + weightedOps := simulation.WeightedOperations(appParams, cdc, app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) s := rand.NewSource(1) r := rand.New(s) @@ -43,11 +41,11 @@ func TestWeightedOperations(t *testing.T) { opMsgRoute string opMsgName string }{ - {farmingparams.DefaultWeightMsgCreateFixedAmountPlan, types.ModuleName, types.TypeMsgCreateFixedAmountPlan}, - {farmingparams.DefaultWeightMsgCreateRatioPlan, types.ModuleName, types.TypeMsgCreateRatioPlan}, - {farmingparams.DefaultWeightMsgStake, types.ModuleName, types.TypeMsgStake}, - {farmingparams.DefaultWeightMsgUnstake, types.ModuleName, types.TypeMsgUnstake}, - {farmingparams.DefaultWeightMsgHarvest, types.ModuleName, types.TypeMsgHarvest}, + {params.DefaultWeightMsgCreateFixedAmountPlan, types.ModuleName, types.TypeMsgCreateFixedAmountPlan}, + {params.DefaultWeightMsgCreateRatioPlan, types.ModuleName, types.TypeMsgCreateRatioPlan}, + {params.DefaultWeightMsgStake, types.ModuleName, types.TypeMsgStake}, + {params.DefaultWeightMsgUnstake, types.ModuleName, types.TypeMsgUnstake}, + {params.DefaultWeightMsgHarvest, types.ModuleName, types.TypeMsgHarvest}, } for i, w := range weightedOps { @@ -95,7 +93,7 @@ func TestSimulateMsgCreateFixedAmountPlan(t *testing.T) { require.Equal(t, "simulation", msg.Name) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) - require.Equal(t, "476941318stake", msg.EpochAmount.String()) + require.Equal(t, "656203300pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", msg.EpochAmount.String()) require.Len(t, futureOperations, 0) } @@ -133,7 +131,7 @@ func TestSimulateMsgCreateRatioPlan(t *testing.T) { require.Equal(t, "simulation", msg.Name) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) - require.Equal(t, "0.700000000000000000", msg.EpochRatio.String()) + require.Equal(t, "0.500000000000000000", msg.EpochRatio.String()) require.Len(t, futureOperations, 0) } @@ -169,7 +167,48 @@ func TestSimulateMsgStake(t *testing.T) { require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgStake, msg.Type()) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) - require.Equal(t, "89941318stake", msg.StakingCoins.String()) + require.Equal(t, "476941318stake", msg.StakingCoins.String()) + require.Len(t, futureOperations, 0) +} + +// TestSimulateMsgUnstake tests the normal scenario of a valid message of type TypeMsgUnstake. +// Abnormal scenarios, where the message are created by an errors are not tested here. +func TestSimulateMsgUnstake(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // setup randomly generated staking creation fees + feeCoins := simulation.GenStakingCreationFee(r) + params := app.FarmingKeeper.GetParams(ctx) + params.StakingCreationFee = feeCoins + app.FarmingKeeper.SetParams(ctx, params) + + // staking must exist in order to simulate unstake + stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) + _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) + require.NoError(t, err) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgUnstake(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(t, err) + + var msg types.MsgUnstake + err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + require.NoError(t, err) + + require.True(t, operationMsg.OK) + require.Equal(t, types.TypeMsgUnstake, msg.Type()) + require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) + require.Equal(t, "89941318stake", msg.UnstakingCoins.String()) require.Len(t, futureOperations, 0) } @@ -189,27 +228,32 @@ func TestSimulateMsgHarvest(t *testing.T) { params.EpochDays = 1 app.FarmingKeeper.SetParams(ctx, params) - // set fixed amountplan - plan := types.NewFixedAmountPlan( - types.NewBasePlan( - 1, - "simulation", - types.PlanTypePrivate, - accounts[0].Address.String(), - accounts[0].Address.String(), - sdk.NewDecCoins( - sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% - ), - mustParseRFC3339("2021-08-01T00:00:00Z"), - mustParseRFC3339("2021-08-31T00:00:00Z"), + // setup a fixed amount plan + msgPlan := &types.MsgCreateFixedAmountPlan{ + Name: "simulation", + Creator: accounts[0].Address.String(), + StakingCoinWeights: sdk.NewDecCoins( + sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% ), - sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), + StartTime: mustParseRFC3339("2021-08-01T00:00:00Z"), + EndTime: mustParseRFC3339("2021-08-31T00:00:00Z"), + EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), + } + + app.FarmingKeeper.CreateFixedAmountPlan( + ctx, + msgPlan, + accounts[0].Address, + accounts[0].Address, + types.PlanTypePrivate, ) - app.FarmingKeeper.SetPlan(ctx, plan) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) // set staking and the amount must be greater than the randomized value range for unharvest - amount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 100_000_000, 1000_000_000)))) - _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, amount) + stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) + _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) require.NoError(t, err) app.FarmingKeeper.ProcessQueuedCoins(ctx) @@ -217,8 +261,11 @@ func TestSimulateMsgHarvest(t *testing.T) { err = app.FarmingKeeper.DistributeRewards(ctx) require.NoError(t, err) - // begin a new block - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + // check that queue coins are moved to staked coins + staking, found := app.FarmingKeeper.GetStaking(ctx, uint64(1)) + require.Equal(t, true, found) + require.Equal(t, true, staking.QueuedCoins.IsZero()) + require.Equal(t, true, staking.StakedCoins.IsAllPositive()) // execute operation op := simulation.SimulateMsgHarvest(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) @@ -270,3 +317,73 @@ func mustParseRFC3339(s string) time.Time { } return t } + +func TestHarvest(t *testing.T) { + app, ctx := createTestApp(false) + + // setup a single account + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // mint + var mintCoins sdk.Coins + for _, denom := range []string{ + "pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", + } { + mintCoins = mintCoins.Add(sdk.NewInt64Coin(denom, int64(simtypes.RandIntBetween(r, 1e14, 1e15)))) + } + + err := app.BankKeeper.MintCoins(ctx, types.ModuleName, mintCoins) + require.NoError(t, err) + + err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accounts[0].Address, mintCoins) + require.NoError(t, err) + + // setup epoch days to 1 + params := app.FarmingKeeper.GetParams(ctx) + params.EpochDays = 1 + app.FarmingKeeper.SetParams(ctx, params) + + // setup a fixed amount plan + msgPlan := &types.MsgCreateFixedAmountPlan{ + Name: "simulation", + Creator: accounts[0].Address.String(), + StakingCoinWeights: sdk.NewDecCoins( + sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% + ), + StartTime: ctx.BlockTime(), + EndTime: ctx.BlockTime().AddDate(0, 1, 0), + EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", 1)), + } + + app.FarmingKeeper.CreateFixedAmountPlan( + ctx, + msgPlan, + accounts[0].Address, + accounts[0].Address, + types.PlanTypePrivate, + ) + + // staking must exist in order to simulate unstake + _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))) + require.NoError(t, err) + + // increase epoch days and distribute rewards + app.FarmingKeeper.ProcessQueuedCoins(ctx) + ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 5)) + err = app.FarmingKeeper.DistributeRewards(ctx) + require.NoError(t, err) + app.FarmingKeeper.SetLastEpochTime(ctx, ctx.BlockTime()) + + plans := app.FarmingKeeper.GetAllPlans(ctx) + stakings := app.FarmingKeeper.GetAllStakings(ctx) + rewards := app.FarmingKeeper.GetAllRewards(ctx) + fmt.Println("") + fmt.Println(plans[0]) + fmt.Println("") + fmt.Println(stakings[0]) + fmt.Println("") + fmt.Println(rewards) +} From 9fcb8e3e3003317da2294a1fb62177cf4679d7e8 Mon Sep 17 00:00:00 2001 From: kogisin Date: Mon, 6 Sep 2021 21:22:41 +0900 Subject: [PATCH 17/31] feat: adding public plan proposals --- x/farming/simulation/operations.go | 39 +++++++++++++++++++++++++++ x/farming/simulation/proposals.go | 43 ++++++++++++++++++------------ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 1c5e5b61..39488fba 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -1,6 +1,7 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" @@ -357,6 +358,44 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke } } +// SimulateMsgSubmitFixedAmountPlanProposal simulates creating a msg CreateFixedAmountPlan Proposal +// voting on the proposal, and subsequently slashing the proposal. It is implemented using +// future operations. +func SimulateMsgSubmitFixedAmountPlanProposal( + ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, contentSim simtypes.ContentSimulatorFn, +) simtypes.Operation { + // The states are: + // column 1: All validators vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: noone votes + // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, + // feel free to change. + numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + }) + + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + fmt.Println("numVotesTransitionMatrix: ", numVotesTransitionMatrix) + fmt.Println("statePercentageArray: ", statePercentageArray) + fmt.Println("curNumVotesState: ", curNumVotesState) + return simtypes.OperationMsg{}, nil, nil + } +} + // mintPoolCoins mints random amount of coins with the provided pool coin denoms and // send them to the simulated account. func mintPoolCoins(ctx sdk.Context, r *rand.Rand, bk types.BankKeeper, acc simtypes.Account) (mintCoins sdk.Coins, err error) { diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go index 5458ecdd..e58e1113 100644 --- a/x/farming/simulation/proposals.go +++ b/x/farming/simulation/proposals.go @@ -3,32 +3,41 @@ package simulation import ( "math/rand" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/tendermint/farming/x/farming/keeper" ) -// OpWeightSubmitPublicPlanProposals app params key for public plan proposals -const OpWeightSubmitPublicPlanProposals = "op_weight_submit_public_plan_proposals" +// Simulation operation weights constants. +const ( + OpWeightSubmitPublicFixedAmountPlanProposal = "op_weight_submit_public_fixed_amount_plan_proposal" + OpWeightSubmitPublicRatioPlanProposal = "op_weight_submit_public_ratio_plan_proposal" +) // ProposalContents defines the module weighted proposals' contents func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { - // TODO: not implemented yet - return nil - // return []simtypes.WeightedProposalContent{ - // simulation.NewWeightedProposalContent( - // OpWeightSubmitPublicPlanProposals, - // simappparams.DefaultWeightPublicPlanProposals, - // SimulateCommunityPoolSpendProposalContent(k), - // ), - // } + return []simtypes.WeightedProposalContent{ + simulation.NewWeightedProposalContent( + OpWeightSubmitPublicFixedAmountPlanProposal, + simappparams.DefaultWeightTextProposal, + SimulateTextProposalContent, + ), + // simulation.NewWeightedProposalContent( + // OpWeightSubmitPublicRatioPlanProposal, + // simappparams.DefaultWeightTextProposal, + // SimulateTextProposalContent(k), + // ), + } } -// SimulatePublicPlanProposalsProposalContent generates random public plan proposals proposal content -func SimulatePublicPlanProposalsProposalContent(k keeper.Keeper) simtypes.ContentSimulatorFn { - return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { - // TODO: not implemented yet - return nil - } +// SimulateTextProposalContent returns a random text proposal content. +func SimulateTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) simtypes.Content { + return govtypes.NewTextProposal( + simtypes.RandStringOfLength(r, 140), + simtypes.RandStringOfLength(r, 5000), + ) } From 13e740698a672bcb4a4e2b71fbd9bc7f40cc059b Mon Sep 17 00:00:00 2001 From: kogisin Date: Wed, 8 Sep 2021 15:34:26 +0900 Subject: [PATCH 18/31] chore: adding simulation for public plan proposals work in progress --- app/params/weights.go | 2 ++ x/farming/client/cli/tx.go | 12 +++++---- x/farming/simulation/operations.go | 39 ------------------------------ x/farming/simulation/proposals.go | 39 +++++++++++++++--------------- x/farming/types/proposal.go | 11 ++++++--- 5 files changed, 36 insertions(+), 67 deletions(-) diff --git a/app/params/weights.go b/app/params/weights.go index 30c44cd9..5786da1f 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -7,4 +7,6 @@ const ( DefaultWeightMsgStake int = 85 DefaultWeightMsgUnstake int = 30 DefaultWeightMsgHarvest int = 30 + + DefaultWeightPublicPlanProposal int = 5 ) diff --git a/x/farming/client/cli/tx.go b/x/farming/client/cli/tx.go index b7f33163..5c2fa2fa 100644 --- a/x/farming/client/cli/tx.go +++ b/x/farming/client/cli/tx.go @@ -376,11 +376,13 @@ Where proposal.json contains: return err } - content, err := types.NewPublicPlanProposal(proposal.Title, proposal.Description, - proposal.AddRequestProposals, proposal.UpdateRequestProposals, proposal.DeleteRequestProposals) - if err != nil { - return err - } + content := types.NewPublicPlanProposal( + proposal.Title, + proposal.Description, + proposal.AddRequestProposals, + proposal.UpdateRequestProposals, + proposal.DeleteRequestProposals, + ) from := clientCtx.GetFromAddress() diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 39488fba..1c5e5b61 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -1,7 +1,6 @@ package simulation import ( - "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" @@ -358,44 +357,6 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke } } -// SimulateMsgSubmitFixedAmountPlanProposal simulates creating a msg CreateFixedAmountPlan Proposal -// voting on the proposal, and subsequently slashing the proposal. It is implemented using -// future operations. -func SimulateMsgSubmitFixedAmountPlanProposal( - ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, contentSim simtypes.ContentSimulatorFn, -) simtypes.Operation { - // The states are: - // column 1: All validators vote - // column 2: 90% vote - // column 3: 75% vote - // column 4: 40% vote - // column 5: 15% vote - // column 6: noone votes - // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, - // feel free to change. - numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ - {20, 10, 0, 0, 0, 0}, - {55, 50, 20, 10, 0, 0}, - {25, 25, 30, 25, 30, 15}, - {0, 15, 30, 25, 30, 30}, - {0, 0, 20, 30, 30, 30}, - {0, 0, 0, 10, 10, 25}, - }) - - statePercentageArray := []float64{1, .9, .75, .4, .15, 0} - curNumVotesState := 1 - - return func( - r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - fmt.Println("numVotesTransitionMatrix: ", numVotesTransitionMatrix) - fmt.Println("statePercentageArray: ", statePercentageArray) - fmt.Println("curNumVotesState: ", curNumVotesState) - return simtypes.OperationMsg{}, nil, nil - } -} - // mintPoolCoins mints random amount of coins with the provided pool coin denoms and // send them to the simulated account. func mintPoolCoins(ctx sdk.Context, r *rand.Rand, bk types.BankKeeper, acc simtypes.Account) (mintCoins sdk.Coins, err error) { diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go index e58e1113..40f29604 100644 --- a/x/farming/simulation/proposals.go +++ b/x/farming/simulation/proposals.go @@ -3,41 +3,40 @@ package simulation import ( "math/rand" - simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/tendermint/farming/app/params" "github.com/tendermint/farming/x/farming/keeper" + "github.com/tendermint/farming/x/farming/types" ) // Simulation operation weights constants. -const ( - OpWeightSubmitPublicFixedAmountPlanProposal = "op_weight_submit_public_fixed_amount_plan_proposal" - OpWeightSubmitPublicRatioPlanProposal = "op_weight_submit_public_ratio_plan_proposal" -) +const OpWeightSimulatePublicPlanProposal = "op_weight_public_plan_proposal" // ProposalContents defines the module weighted proposals' contents func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { return []simtypes.WeightedProposalContent{ simulation.NewWeightedProposalContent( - OpWeightSubmitPublicFixedAmountPlanProposal, - simappparams.DefaultWeightTextProposal, - SimulateTextProposalContent, + OpWeightSimulatePublicPlanProposal, + params.DefaultWeightPublicPlanProposal, + SimulatePublicPlanProposal(k), ), - // simulation.NewWeightedProposalContent( - // OpWeightSubmitPublicRatioPlanProposal, - // simappparams.DefaultWeightTextProposal, - // SimulateTextProposalContent(k), - // ), } } -// SimulateTextProposalContent returns a random text proposal content. -func SimulateTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) simtypes.Content { - return govtypes.NewTextProposal( - simtypes.RandStringOfLength(r, 140), - simtypes.RandStringOfLength(r, 5000), - ) +// SimulatePublicPlanProposal generates random public plan proposal content +func SimulatePublicPlanProposal(k keeper.Keeper) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + // simAccount, _ := simtypes.RandomAcc(r, accs) + + return types.NewPublicPlanProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 100), + []*types.AddRequestProposal{}, + []*types.UpdateRequestProposal{}, + []*types.DeleteRequestProposal{}, + ) + } } diff --git a/x/farming/types/proposal.go b/x/farming/types/proposal.go index f3d4523c..7f00fd04 100644 --- a/x/farming/types/proposal.go +++ b/x/farming/types/proposal.go @@ -20,15 +20,20 @@ func init() { gov.RegisterProposalTypeCodec(&PublicPlanProposal{}, "cosmos-sdk/PublicPlanProposal") } -func NewPublicPlanProposal(title, description string, addReq []*AddRequestProposal, - updateReq []*UpdateRequestProposal, deleteReq []*DeleteRequestProposal) (gov.Content, error) { +func NewPublicPlanProposal( + title string, + description string, + addReq []*AddRequestProposal, + updateReq []*UpdateRequestProposal, + deleteReq []*DeleteRequestProposal, +) gov.Content { return &PublicPlanProposal{ Title: title, Description: description, AddRequestProposals: addReq, UpdateRequestProposals: updateReq, DeleteRequestProposals: deleteReq, - }, nil + } } func (p *PublicPlanProposal) GetTitle() string { return p.Title } From a9567eb594d87a85c3e68a817517ed4f3b243121 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 9 Sep 2021 00:32:53 +0900 Subject: [PATCH 19/31] fix: add check for duplicate name value when creating private plans #101 --- x/farming/keeper/genesis_test.go | 6 ++-- x/farming/keeper/msg_server.go | 16 +++++++++ x/farming/keeper/proposal_handler.go | 5 ++- x/farming/module.go | 2 +- x/farming/simulation/operations.go | 4 +-- x/farming/simulation/proposals.go | 46 ++++++++++++++++++++++--- x/farming/types/genesis.go | 14 ++++++-- x/farming/types/plan.go | 30 ++++++++++++----- x/farming/types/plan_test.go | 50 ++++++++++++++++++++++++---- 9 files changed, 144 insertions(+), 29 deletions(-) diff --git a/x/farming/keeper/genesis_test.go b/x/farming/keeper/genesis_test.go index eba97392..21118243 100644 --- a/x/farming/keeper/genesis_test.go +++ b/x/farming/keeper/genesis_test.go @@ -1,6 +1,8 @@ package keeper_test import ( + _ "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/farming/x/farming/types" @@ -11,7 +13,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { types.NewFixedAmountPlan( types.NewBasePlan( 1, - "", + "name1", types.PlanTypePrivate, suite.addrs[0].String(), suite.addrs[0].String(), @@ -25,7 +27,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { types.NewRatioPlan( types.NewBasePlan( 2, - "", + "name2", types.PlanTypePublic, suite.addrs[0].String(), suite.addrs[0].String(), diff --git a/x/farming/keeper/msg_server.go b/x/farming/keeper/msg_server.go index e851a896..9d121d93 100644 --- a/x/farming/keeper/msg_server.go +++ b/x/farming/keeper/msg_server.go @@ -32,10 +32,16 @@ func (k msgServer) CreateFixedAmountPlan(goCtx context.Context, msg *types.MsgCr if err != nil { return nil, err } + if _, err := k.Keeper.CreateFixedAmountPlan(ctx, msg, poolAcc, msg.GetCreator(), types.PlanTypePrivate); err != nil { return nil, err } + plans := k.GetAllPlans(ctx) + if err := types.ValidateName(plans); err != nil { + return nil, err + } + return &types.MsgCreateFixedAmountPlanResponse{}, nil } @@ -46,10 +52,20 @@ func (k msgServer) CreateRatioPlan(goCtx context.Context, msg *types.MsgCreateRa if err != nil { return nil, err } + if _, err := k.Keeper.CreateRatioPlan(ctx, msg, poolAcc, msg.GetCreator(), types.PlanTypePrivate); err != nil { return nil, err } + plans := k.GetAllPlans(ctx) + if err := types.ValidateName(plans); err != nil { + return nil, err + } + + if err := types.ValidateTotalEpochRatio(plans); err != nil { + return nil, err + } + return &types.MsgCreateRatioPlanResponse{}, nil } diff --git a/x/farming/keeper/proposal_handler.go b/x/farming/keeper/proposal_handler.go index 972883fd..6b1234a4 100644 --- a/x/farming/keeper/proposal_handler.go +++ b/x/farming/keeper/proposal_handler.go @@ -28,7 +28,10 @@ func HandlePublicPlanProposal(ctx sdk.Context, k Keeper, proposal *types.PublicP } plans := k.GetAllPlans(ctx) - if err := types.ValidateRatioPlans(plans); err != nil { + if err := types.ValidateName(plans); err != nil { + return err + } + if err := types.ValidateTotalEpochRatio(plans); err != nil { return err } diff --git a/x/farming/module.go b/x/farming/module.go index 4c00a786..f2b0c627 100644 --- a/x/farming/module.go +++ b/x/farming/module.go @@ -189,7 +189,7 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { // ProposalContents returns all the farming content functions used to // simulate governance proposals. func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { - return simulation.ProposalContents(am.keeper) + return simulation.ProposalContents(am.accountKeeper, am.bankKeeper, am.keeper) } // RandomizedParams creates randomized farming param changes for the simulator. diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 1c5e5b61..b7f402ae 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -111,7 +111,7 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil } - name := "simulation" + name := "simulation-test-" + simtypes.RandStringOfLength(r, 5) // name must be unique creatorAcc := account.GetAddress() stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) startTime := ctx.BlockTime() @@ -170,7 +170,7 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil } - name := "simulation" + name := "simulation-test-" + simtypes.RandStringOfLength(r, 5) // name must be unique creatorAcc := account.GetAddress() stakingCoinWeights := sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)) startTime := ctx.BlockTime() diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go index 40f29604..04f3aede 100644 --- a/x/farming/simulation/proposals.go +++ b/x/farming/simulation/proposals.go @@ -16,25 +16,61 @@ import ( const OpWeightSimulatePublicPlanProposal = "op_weight_public_plan_proposal" // ProposalContents defines the module weighted proposals' contents -func ProposalContents(k keeper.Keeper) []simtypes.WeightedProposalContent { +func ProposalContents(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) []simtypes.WeightedProposalContent { return []simtypes.WeightedProposalContent{ simulation.NewWeightedProposalContent( OpWeightSimulatePublicPlanProposal, params.DefaultWeightPublicPlanProposal, - SimulatePublicPlanProposal(k), + SimulatePublicPlanProposal(ak, bk, k), ), } } // SimulatePublicPlanProposal generates random public plan proposal content -func SimulatePublicPlanProposal(k keeper.Keeper) simtypes.ContentSimulatorFn { +func SimulatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.ContentSimulatorFn { return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { - // simAccount, _ := simtypes.RandomAcc(r, accs) + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + params := k.GetParams(ctx) + _, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + if hasNeg { + return nil + } + + poolCoins, err := mintPoolCoins(ctx, r, bk, simAccount) + if err != nil { + return nil + } + + // create add request proposal + // TODO: randomized values of the fields + req := &types.AddRequestProposal{ + Name: "simulation-test-" + simtypes.RandStringOfLength(r, 5), + FarmingPoolAddress: simAccount.Address.String(), + TerminationAddress: simAccount.Address.String(), + StakingCoinWeights: sdk.NewDecCoins(sdk.NewInt64DecCoin(sdk.DefaultBondDenom, 1)), + StartTime: ctx.BlockTime(), + EndTime: ctx.BlockTime().AddDate(0, 1, 0), + EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(poolCoins[r.Intn(3)].Denom, int64(simtypes.RandIntBetween(r, 10_000_000, 1_000_000_000)))), + EpochRatio: sdk.ZeroDec(), + } + addRequests := []*types.AddRequestProposal{req} + + // TODO + // create update request proposal + // updating plan can only be allowed (owner) + + // TODO + // create delete request proposal + // deleting plan can only be allowed return types.NewPublicPlanProposal( simtypes.RandStringOfLength(r, 10), simtypes.RandStringOfLength(r, 100), - []*types.AddRequestProposal{}, + addRequests, []*types.UpdateRequestProposal{}, []*types.DeleteRequestProposal{}, ) diff --git a/x/farming/types/genesis.go b/x/farming/types/genesis.go index 3af6ee53..40548284 100644 --- a/x/farming/types/genesis.go +++ b/x/farming/types/genesis.go @@ -39,7 +39,9 @@ func ValidateGenesis(data GenesisState) error { if err := data.Params.Validate(); err != nil { return err } + id := uint64(0) + var plans []PlanI for _, record := range data.PlanRecords { if err := record.Validate(); err != nil { @@ -54,8 +56,12 @@ func ValidateGenesis(data GenesisState) error { } plans = append(plans, plan) } - err := ValidateRatioPlans(plans) - if err != nil { + + if err := ValidateName(plans); err != nil { + return err + } + + if err := ValidateTotalEpochRatio(plans); err != nil { return err } @@ -64,17 +70,21 @@ func ValidateGenesis(data GenesisState) error { return err } } + for _, reward := range data.Rewards { if err := reward.Validate(); err != nil { return err } } + if err := data.RewardPoolCoins.Validate(); err != nil { return err } + if err := data.StakingReserveCoins.Validate(); err != nil { return err } + return nil } diff --git a/x/farming/types/plan.go b/x/farming/types/plan.go index 63198b17..35be3375 100644 --- a/x/farming/types/plan.go +++ b/x/farming/types/plan.go @@ -249,17 +249,34 @@ type PlanI interface { Validate() error } -// ValidateRatioPlans validates a farmer's total epoch ratio and plan name. -// A total epoch ratio cannot be higher than 1 and plan name must not be duplicate. -func ValidateRatioPlans(i interface{}) error { +// ValidateName validates duplicate plan name value. +func ValidateName(i interface{}) error { plans, ok := i.([]PlanI) if !ok { return sdkerrors.Wrapf(ErrInvalidPlanType, "invalid plan type %T", i) } - totalEpochRatio := make(map[string]sdk.Dec) names := make(map[string]bool) + for _, plan := range plans { + if _, ok := names[plan.GetName()]; ok { + return sdkerrors.Wrap(ErrDuplicatePlanName, plan.GetName()) + } + names[plan.GetName()] = true + } + + return nil +} + +// ValidateTotalEpochRatio validates a farmer's total epoch ratio that must be equal to 1. +func ValidateTotalEpochRatio(i interface{}) error { + plans, ok := i.([]PlanI) + if !ok { + return sdkerrors.Wrapf(ErrInvalidPlanType, "invalid plan type %T", i) + } + + totalEpochRatio := make(map[string]sdk.Dec) + for _, plan := range plans { farmingPoolAddr := plan.GetFarmingPoolAddress().String() @@ -273,11 +290,6 @@ func ValidateRatioPlans(i interface{}) error { } else { totalEpochRatio[farmingPoolAddr] = plan.EpochRatio } - - if _, ok := names[plan.Name]; ok { - return sdkerrors.Wrap(ErrDuplicatePlanName, plan.Name) - } - names[plan.Name] = true } } diff --git a/x/farming/types/plan_test.go b/x/farming/types/plan_test.go index ea19c256..72a57626 100644 --- a/x/farming/types/plan_test.go +++ b/x/farming/types/plan_test.go @@ -13,9 +13,8 @@ import ( "github.com/tendermint/farming/x/farming/types" ) -func TestRatioPlans(t *testing.T) { - name1 := "testPlan1" - name2 := "testPlan2" +func TestPlanName(t *testing.T) { + name := "testPlan1" farmingPoolAddr1 := sdk.AccAddress("farmingPoolAddr1") terminationAddr1 := sdk.AccAddress("terminationAddr1") stakingCoinWeights := sdk.NewDecCoins( @@ -31,7 +30,7 @@ func TestRatioPlans(t *testing.T) { { []types.PlanI{ types.NewRatioPlan( - types.NewBasePlan(1, name1, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), + types.NewBasePlan(1, name, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), sdk.NewDec(1), ), }, @@ -40,15 +39,52 @@ func TestRatioPlans(t *testing.T) { { []types.PlanI{ types.NewRatioPlan( - types.NewBasePlan(1, name1, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), + types.NewBasePlan(1, name, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), + sdk.NewDec(1), + ), + types.NewRatioPlan( + types.NewBasePlan(1, name, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), sdk.NewDec(1), ), + }, + sdkerrors.Wrap(types.ErrDuplicatePlanName, name), + }, + } + + for _, tc := range testCases { + err := types.ValidateName(tc.plans) + if tc.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, tc.expectedErr.Error(), err.Error()) + } + } +} + +func TestTotalEpochRatio(t *testing.T) { + name1 := "testPlan1" + name2 := "testPlan2" + farmingPoolAddr1 := sdk.AccAddress("farmingPoolAddr1") + terminationAddr1 := sdk.AccAddress("terminationAddr1") + stakingCoinWeights := sdk.NewDecCoins( + sdk.DecCoin{Denom: "testFarmStakingCoinDenom", Amount: sdk.MustNewDecFromStr("1.0")}, + ) + startTime := time.Now().UTC() + endTime := startTime.AddDate(1, 0, 0) + + testCases := []struct { + plans []types.PlanI + expectedErr error + }{ + { + []types.PlanI{ types.NewRatioPlan( types.NewBasePlan(1, name1, 1, farmingPoolAddr1.String(), terminationAddr1.String(), stakingCoinWeights, startTime, endTime), sdk.NewDec(1), ), }, - sdkerrors.Wrap(types.ErrDuplicatePlanName, name1), + nil, }, { []types.PlanI{ @@ -66,7 +102,7 @@ func TestRatioPlans(t *testing.T) { } for _, tc := range testCases { - err := types.ValidateRatioPlans(tc.plans) + err := types.ValidateTotalEpochRatio(tc.plans) if tc.expectedErr == nil { require.NoError(t, err) } else { From ff5f461cb542ebba2e1bab771512dfade7ec7b21 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 9 Sep 2021 15:28:13 +0900 Subject: [PATCH 20/31] test: fix broken tests --- x/farming/simulation/operations_test.go | 156 ++++++++++++------------ 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index cef74d7d..598b9812 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -1,7 +1,6 @@ package simulation_test import ( - "fmt" "math/rand" "testing" "time" @@ -90,10 +89,10 @@ func TestSimulateMsgCreateFixedAmountPlan(t *testing.T) { require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgCreateFixedAmountPlan, msg.Type()) - require.Equal(t, "simulation", msg.Name) + require.Equal(t, "simulation-test-GkqEG", msg.Name) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) - require.Equal(t, "656203300pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", msg.EpochAmount.String()) + require.Equal(t, "126410694pool3036F43CB8131A1A63D2B3D3B11E9CF6FA2A2B6FEC17D5AD283C25C939614A8C", msg.EpochAmount.String()) require.Len(t, futureOperations, 0) } @@ -128,10 +127,10 @@ func TestSimulateMsgCreateRatioPlan(t *testing.T) { require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgCreateRatioPlan, msg.Type()) - require.Equal(t, "simulation", msg.Name) + require.Equal(t, "simulation-test-GkqEG", msg.Name) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Creator) require.Equal(t, "1.000000000000000000stake", msg.StakingCoinWeights.String()) - require.Equal(t, "0.500000000000000000", msg.EpochRatio.String()) + require.Equal(t, "0.700000000000000000", msg.EpochRatio.String()) require.Len(t, futureOperations, 0) } @@ -240,20 +239,21 @@ func TestSimulateMsgHarvest(t *testing.T) { EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), } - app.FarmingKeeper.CreateFixedAmountPlan( + _, err := app.FarmingKeeper.CreateFixedAmountPlan( ctx, msgPlan, accounts[0].Address, accounts[0].Address, types.PlanTypePrivate, ) + require.NoError(t, err) // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) // set staking and the amount must be greater than the randomized value range for unharvest stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) - _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) + _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) require.NoError(t, err) app.FarmingKeeper.ProcessQueuedCoins(ctx) @@ -318,72 +318,76 @@ func mustParseRFC3339(s string) time.Time { return t } -func TestHarvest(t *testing.T) { - app, ctx := createTestApp(false) - - // setup a single account - s := rand.NewSource(1) - r := rand.New(s) - - accounts := getTestingAccounts(t, r, app, ctx, 1) - - // mint - var mintCoins sdk.Coins - for _, denom := range []string{ - "pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", - } { - mintCoins = mintCoins.Add(sdk.NewInt64Coin(denom, int64(simtypes.RandIntBetween(r, 1e14, 1e15)))) - } - - err := app.BankKeeper.MintCoins(ctx, types.ModuleName, mintCoins) - require.NoError(t, err) - - err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accounts[0].Address, mintCoins) - require.NoError(t, err) - - // setup epoch days to 1 - params := app.FarmingKeeper.GetParams(ctx) - params.EpochDays = 1 - app.FarmingKeeper.SetParams(ctx, params) - - // setup a fixed amount plan - msgPlan := &types.MsgCreateFixedAmountPlan{ - Name: "simulation", - Creator: accounts[0].Address.String(), - StakingCoinWeights: sdk.NewDecCoins( - sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% - ), - StartTime: ctx.BlockTime(), - EndTime: ctx.BlockTime().AddDate(0, 1, 0), - EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", 1)), - } - - app.FarmingKeeper.CreateFixedAmountPlan( - ctx, - msgPlan, - accounts[0].Address, - accounts[0].Address, - types.PlanTypePrivate, - ) - - // staking must exist in order to simulate unstake - _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))) - require.NoError(t, err) - - // increase epoch days and distribute rewards - app.FarmingKeeper.ProcessQueuedCoins(ctx) - ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 5)) - err = app.FarmingKeeper.DistributeRewards(ctx) - require.NoError(t, err) - app.FarmingKeeper.SetLastEpochTime(ctx, ctx.BlockTime()) - - plans := app.FarmingKeeper.GetAllPlans(ctx) - stakings := app.FarmingKeeper.GetAllStakings(ctx) - rewards := app.FarmingKeeper.GetAllRewards(ctx) - fmt.Println("") - fmt.Println(plans[0]) - fmt.Println("") - fmt.Println(stakings[0]) - fmt.Println("") - fmt.Println(rewards) -} +// +// test +// +// func TestHarvest(t *testing.T) { +// app, ctx := createTestApp(false) + +// // setup a single account +// s := rand.NewSource(1) +// r := rand.New(s) + +// accounts := getTestingAccounts(t, r, app, ctx, 1) + +// // mint +// var mintCoins sdk.Coins +// for _, denom := range []string{ +// "pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", +// } { +// mintCoins = mintCoins.Add(sdk.NewInt64Coin(denom, int64(simtypes.RandIntBetween(r, 1e14, 1e15)))) +// } + +// err := app.BankKeeper.MintCoins(ctx, types.ModuleName, mintCoins) +// require.NoError(t, err) + +// err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accounts[0].Address, mintCoins) +// require.NoError(t, err) + +// // setup epoch days to 1 +// params := app.FarmingKeeper.GetParams(ctx) +// params.EpochDays = 1 +// app.FarmingKeeper.SetParams(ctx, params) + +// // setup a fixed amount plan +// msgPlan := &types.MsgCreateFixedAmountPlan{ +// Name: "simulation", +// Creator: accounts[0].Address.String(), +// StakingCoinWeights: sdk.NewDecCoins( +// sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% +// ), +// StartTime: ctx.BlockTime(), +// EndTime: ctx.BlockTime().AddDate(0, 1, 0), +// EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", 1)), +// } + +// _, err := app.FarmingKeeper.CreateFixedAmountPlan( +// ctx, +// msgPlan, +// accounts[0].Address, +// accounts[0].Address, +// types.PlanTypePrivate, +// ) +// require.NoError(t, err) + +// // staking must exist in order to simulate unstake +// _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))) +// require.NoError(t, err) + +// // increase epoch days and distribute rewards +// app.FarmingKeeper.ProcessQueuedCoins(ctx) +// ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 5)) +// err = app.FarmingKeeper.DistributeRewards(ctx) +// require.NoError(t, err) +// app.FarmingKeeper.SetLastEpochTime(ctx, ctx.BlockTime()) + +// plans := app.FarmingKeeper.GetAllPlans(ctx) +// stakings := app.FarmingKeeper.GetAllStakings(ctx) +// rewards := app.FarmingKeeper.GetAllRewards(ctx) +// fmt.Println("") +// fmt.Println(plans[0]) +// fmt.Println("") +// fmt.Println(stakings[0]) +// fmt.Println("") +// fmt.Println(rewards) +// } From 568dec8001b19b50be4c9c78548a706d083dca91 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 9 Sep 2021 18:50:38 +0900 Subject: [PATCH 21/31] chore: remove comments, refactor logic, fixing harvest --- x/farming/simulation/decoder_test.go | 25 ------------------------- x/farming/simulation/genesis_test.go | 4 ++-- x/farming/simulation/operations.go | 25 +++++++++++++++++-------- x/farming/simulation/operations_test.go | 9 +++++++-- x/farming/simulation/params_test.go | 4 ++-- 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index 32619f3a..68e35648 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -15,31 +15,6 @@ import ( "github.com/tendermint/farming/x/farming/types" ) -// case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix): -// var pA, pB types.BasePlan -// cdc.MustUnmarshal(kvA.Value, &pA) -// cdc.MustUnmarshal(kvA.Value, &pB) -// return fmt.Sprintf("%v\n%v", pA, pB) - -// case bytes.Equal(kvA.Key[:1], types.PlansByFarmerIndexKeyPrefix), -// bytes.Equal(kvA.Key[:1], types.StakingByFarmerIndexKeyPrefix), -// bytes.Equal(kvA.Key[:1], types.RewardsByFarmerIndexKeyPrefix): -// return fmt.Sprintf("%v\n%v", sdk.AccAddress(kvA.Value), sdk.AccAddress(kvB.Value)) - -// case bytes.Equal(kvA.Key[:1], types.StakingKeyPrefix): -// var sA, sB types.Staking -// cdc.MustUnmarshal(kvA.Value, &sA) -// cdc.MustUnmarshal(kvA.Value, &sB) -// return fmt.Sprintf("%v\n%v", sA, sB) - -// case bytes.Equal(kvA.Key[:1], types.StakingsByStakingCoinDenomIndexKeyPrefix): -// return fmt.Sprintf("%s\n%s", kvA.Value, kvB.Value) - -// case bytes.Equal(kvA.Key[:1], types.RewardKeyPrefix): -// var rA, rB types.Reward -// cdc.MustUnmarshal(kvA.Value, &rA) -// return fmt.Sprintf("%v\n%v", rA, rB) - var ( pk1 = ed25519.GenPrivKey().PubKey() farmerAddr1 = sdk.AccAddress(pk1.Address()) diff --git a/x/farming/simulation/genesis_test.go b/x/farming/simulation/genesis_test.go index 62688f36..d199def3 100644 --- a/x/farming/simulation/genesis_test.go +++ b/x/farming/simulation/genesis_test.go @@ -40,8 +40,8 @@ func TestRandomizedGenState(t *testing.T) { var genState types.GenesisState simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) - dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(336122540))) - dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(208240456))) + dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(36122540))) + dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(8240456))) dec3 := uint32(7) dec4 := "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index b7f402ae..94341a13 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -106,6 +106,7 @@ func SimulateMsgCreateFixedAmountPlan(ak types.AccountKeeper, bk types.BankKeepe return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "insufficient balance for plan creation fee"), nil, nil } + // mint pool coins to simulate the real-world cases poolCoins, err := mintPoolCoins(ctx, r, bk, simAccount) if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil @@ -165,6 +166,7 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateRatioPlan, "insufficient balance for plan creation fee"), nil, nil } + // mint pool coins to simulate the real-world cases _, err := mintPoolCoins(ctx, r, bk, simAccount) if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateFixedAmountPlan, "unable to mint pool coins"), nil, nil @@ -205,7 +207,7 @@ func SimulateMsgCreateRatioPlan(ak types.AccountKeeper, bk types.BankKeeper, k k } } -// SimulateMsgStake generates a MsgCreateFixedAmountPlan with random values +// SimulateMsgStake generates a MsgStake with random values // nolint: interfacer func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( @@ -264,7 +266,7 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 100_000_000))), ) - // staking must exist in order to unharvest + // staking must exist in order to unstake staking, found := k.GetStakingByFarmer(ctx, farmer) if !found { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), nil, nil @@ -307,15 +309,22 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // // TODO: not fully implemented yet. It needs debugging why // there are no rewards although it processes queued coins and distribute rewards - stakings := k.GetAllStakings(ctx) - if len(stakings) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to find any staking"), nil, nil - } + // var simAccount simtypes.Account - simAccount.Address = stakings[r.Intn(len(stakings))].GetFarmer() + + // find staking from the simulated accounts + var ranStaking types.Staking + for _, acc := range accs { + if staking, found := k.GetStakingByFarmer(ctx, acc.Address); found { + simAccount = acc + ranStaking = staking + break + } + } // process queued coins and distribute rewards relative to the current epoch days params := k.GetParams(ctx) @@ -336,7 +345,7 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke account := ak.GetAccount(ctx, simAccount.Address) spendable := bk.SpendableCoins(ctx, account.GetAddress()) - msg := types.NewMsgHarvest(simAccount.Address, []string{sdk.DefaultBondDenom}) + msg := types.NewMsgHarvest(simAccount.Address, ranStaking.StakingCoinDenoms()) txCtx := simulation.OperationInput{ R: r, diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 598b9812..e58e238b 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -1,6 +1,7 @@ package simulation_test import ( + "fmt" "math/rand" "testing" "time" @@ -222,7 +223,7 @@ func TestSimulateMsgHarvest(t *testing.T) { accounts := getTestingAccounts(t, r, app, ctx, 1) - // setup epoch days to 1 + // setup epoch days to 1 to ease the test params := app.FarmingKeeper.GetParams(ctx) params.EpochDays = 1 app.FarmingKeeper.SetParams(ctx, params) @@ -251,7 +252,7 @@ func TestSimulateMsgHarvest(t *testing.T) { // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - // set staking and the amount must be greater than the randomized value range for unharvest + // set staking stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) require.NoError(t, err) @@ -272,6 +273,10 @@ func TestSimulateMsgHarvest(t *testing.T) { operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") require.NoError(t, err) + fmt.Println("operationMsg: ", operationMsg) + fmt.Println("futureOperations: ", futureOperations) + fmt.Println("err: ", err) + var msg types.MsgHarvest err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) require.NoError(t, err) diff --git a/x/farming/simulation/params_test.go b/x/farming/simulation/params_test.go index a6d4182b..3b13897c 100644 --- a/x/farming/simulation/params_test.go +++ b/x/farming/simulation/params_test.go @@ -19,8 +19,8 @@ func TestParamChanges(t *testing.T) { simValue string subspace string }{ - {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "[{\"denom\":\"stake\",\"amount\":\"298498081\"}]", "farming"}, - {"farming/StakingCreationFee", "StakingCreationFee", "[{\"denom\":\"stake\",\"amount\":\"427131847\"}]", "farming"}, + {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "[{\"denom\":\"stake\",\"amount\":\"98498081\"}]", "farming"}, + {"farming/StakingCreationFee", "StakingCreationFee", "[{\"denom\":\"stake\",\"amount\":\"19727887\"}]", "farming"}, {"farming/EpochDays", "EpochDays", "3", "farming"}, {"farming/FarmingFeeCollector", "FarmingFeeCollector", "\"cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x\"", "farming"}, } From 9723a89fbe9a78ccdc5a42f6487d775444577b33 Mon Sep 17 00:00:00 2001 From: kogisin Date: Thu, 9 Sep 2021 23:42:12 +0900 Subject: [PATCH 22/31] build: update github workflow to split simulation jobs --- .github/workflows/sims.yml | 120 +++++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 15 +---- 2 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/sims.yml diff --git a/.github/workflows/sims.yml b/.github/workflows/sims.yml new file mode 100644 index 00000000..aa8dd73a --- /dev/null +++ b/.github/workflows/sims.yml @@ -0,0 +1,120 @@ +name: Sims +# Sims workflow runs multiple types of simulations (nondeterminism, import-export, after-import) +# This workflow will run on all Pull Requests, if a .go, .mod or .sum file have been changed +on: + pull_request: + push: + branches: + - master + - develop + +jobs: + build: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'skip-sims')" + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2.1.3 + with: + go-version: 1.16 + - name: Display go version + run: go version + - run: make build + + install-runsim: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/setup-go@v2.1.3 + with: + go-version: 1.16 + - name: Display go version + run: go version + - name: Install runsim + run: export GO111MODULE="on" && go get github.com/cosmos/tools/cmd/runsim@v1.0.0 + - uses: actions/cache@v2.1.6 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + + test-sim-nondeterminism: + runs-on: ubuntu-latest + needs: [build, install-runsim] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2.1.3 + with: + go-version: 1.16 + - name: Display go version + run: go version + - uses: technote-space/get-diff-action@v4 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - uses: actions/cache@v2.1.6 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + if: env.GIT_DIFF + - name: test-sim-nondeterminism + run: | + make test-sim-nondeterminism + if: env.GIT_DIFF + + # test-sim-import-export: + # runs-on: ubuntu-latest + # needs: [build, install-runsim] + # steps: + # - uses: actions/checkout@v2 + # - uses: actions/setup-go@v2.1.3 + # with: + # go-version: 1.16 + # - name: Display go version + # run: go version + # - uses: technote-space/get-diff-action@v4 + # with: + # SUFFIX_FILTER: | + # **/**.go + # go.mod + # go.sum + # SET_ENV_NAME_INSERTIONS: 1 + # SET_ENV_NAME_LINES: 1 + # - uses: actions/cache@v2.1.6 + # with: + # path: ~/go/bin + # key: ${{ runner.os }}-go-runsim-binary + # if: env.GIT_DIFF + # - name: test-sim-import-export + # run: | + # make test-sim-import-export + # if: env.GIT_DIFF + + # test-sim-after-import: + # runs-on: ubuntu-latest + # needs: [build, install-runsim] + # steps: + # - uses: actions/checkout@v2 + # - uses: actions/setup-go@v2.1.3 + # with: + # go-version: 1.16 + # - name: Display go version + # run: go version + # - uses: technote-space/get-diff-action@v4 + # with: + # SUFFIX_FILTER: | + # **/**.go + # go.mod + # go.sum + # SET_ENV_NAME_INSERTIONS: 1 + # SET_ENV_NAME_LINES: 1 + # - uses: actions/cache@v2.1.6 + # with: + # path: ~/go/bin + # key: ${{ runner.os }}-go-runsim-binary + # if: env.GIT_DIFF + # - name: test-sim-after-import + # run: | + # make test-sim-after-import + # if: env.GIT_DIFF diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b84ce634..b84b9dba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,17 +87,4 @@ jobs: done - uses: codecov/codecov-action@v1.0.14 with: - file: ./coverage.txt - - test-simulation: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2.1.3 - with: - go-version: 1.15 - - name: Display go version - run: go version - - name: Testing simulation - run: make test-sim-nondeterminism + file: ./coverage.txt \ No newline at end of file From 08ce3b132ddc79466822a1c6e219473139a09cab Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 10 Sep 2021 00:14:00 +0900 Subject: [PATCH 23/31] chore: add public plans, remove unused test codes and clean up logics - add request public plan proposal - update request public plan proposal - delete request public plan proposal - comment TODO to improve public plans logic --- app/params/weights.go | 4 +- x/farming/simulation/operations_test.go | 79 ------------ x/farming/simulation/proposals.go | 155 +++++++++++++++++++++--- x/farming/simulation/proposals_test.go | 84 +++++++++++++ x/farming/types/proposal.go | 2 +- 5 files changed, 227 insertions(+), 97 deletions(-) create mode 100644 x/farming/simulation/proposals_test.go diff --git a/app/params/weights.go b/app/params/weights.go index 5786da1f..9890600d 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -8,5 +8,7 @@ const ( DefaultWeightMsgUnstake int = 30 DefaultWeightMsgHarvest int = 30 - DefaultWeightPublicPlanProposal int = 5 + DefaultWeightAddPublicPlanProposal int = 10 + DefaultWeightUpdatePublicPlanProposal int = 5 + DefaultWeightDeletePublicPlanProposal int = 5 ) diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index e58e238b..7d063f3d 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -1,7 +1,6 @@ package simulation_test import ( - "fmt" "math/rand" "testing" "time" @@ -273,10 +272,6 @@ func TestSimulateMsgHarvest(t *testing.T) { operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") require.NoError(t, err) - fmt.Println("operationMsg: ", operationMsg) - fmt.Println("futureOperations: ", futureOperations) - fmt.Println("err: ", err) - var msg types.MsgHarvest err = app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) require.NoError(t, err) @@ -322,77 +317,3 @@ func mustParseRFC3339(s string) time.Time { } return t } - -// -// test -// -// func TestHarvest(t *testing.T) { -// app, ctx := createTestApp(false) - -// // setup a single account -// s := rand.NewSource(1) -// r := rand.New(s) - -// accounts := getTestingAccounts(t, r, app, ctx, 1) - -// // mint -// var mintCoins sdk.Coins -// for _, denom := range []string{ -// "pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", -// } { -// mintCoins = mintCoins.Add(sdk.NewInt64Coin(denom, int64(simtypes.RandIntBetween(r, 1e14, 1e15)))) -// } - -// err := app.BankKeeper.MintCoins(ctx, types.ModuleName, mintCoins) -// require.NoError(t, err) - -// err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accounts[0].Address, mintCoins) -// require.NoError(t, err) - -// // setup epoch days to 1 -// params := app.FarmingKeeper.GetParams(ctx) -// params.EpochDays = 1 -// app.FarmingKeeper.SetParams(ctx, params) - -// // setup a fixed amount plan -// msgPlan := &types.MsgCreateFixedAmountPlan{ -// Name: "simulation", -// Creator: accounts[0].Address.String(), -// StakingCoinWeights: sdk.NewDecCoins( -// sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% -// ), -// StartTime: ctx.BlockTime(), -// EndTime: ctx.BlockTime().AddDate(0, 1, 0), -// EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("pool93E069B333B5ECEBFE24C6E1437E814003248E0DD7FF8B9F82119F4587449BA5", 1)), -// } - -// _, err := app.FarmingKeeper.CreateFixedAmountPlan( -// ctx, -// msgPlan, -// accounts[0].Address, -// accounts[0].Address, -// types.PlanTypePrivate, -// ) -// require.NoError(t, err) - -// // staking must exist in order to simulate unstake -// _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))) -// require.NoError(t, err) - -// // increase epoch days and distribute rewards -// app.FarmingKeeper.ProcessQueuedCoins(ctx) -// ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, 5)) -// err = app.FarmingKeeper.DistributeRewards(ctx) -// require.NoError(t, err) -// app.FarmingKeeper.SetLastEpochTime(ctx, ctx.BlockTime()) - -// plans := app.FarmingKeeper.GetAllPlans(ctx) -// stakings := app.FarmingKeeper.GetAllStakings(ctx) -// rewards := app.FarmingKeeper.GetAllRewards(ctx) -// fmt.Println("") -// fmt.Println(plans[0]) -// fmt.Println("") -// fmt.Println(stakings[0]) -// fmt.Println("") -// fmt.Println(rewards) -// } diff --git a/x/farming/simulation/proposals.go b/x/farming/simulation/proposals.go index 04f3aede..ca8f4d78 100644 --- a/x/farming/simulation/proposals.go +++ b/x/farming/simulation/proposals.go @@ -12,22 +12,46 @@ import ( "github.com/tendermint/farming/x/farming/types" ) +/* +[TODO]: + We need to come up with better ways to simulate public plan proposals. + Currently, the details are igored and only basic logics are written to simulate. + + These are some of the following considerations that i think need to be discussed and addressed: + 1. Randomize staking coin weights (single or multiple denoms) + 2. Simulate multiple proposals (add new weighted proposal content for multiple plans?) +*/ + // Simulation operation weights constants. -const OpWeightSimulatePublicPlanProposal = "op_weight_public_plan_proposal" +const ( + OpWeightSimulateAddPublicPlanProposal = "op_weight_add_public_plan_proposal" + OpWeightSimulateUpdatePublicPlanProposal = "op_weight_update_public_plan_proposal" + OpWeightSimulateDeletePublicPlanProposal = "op_weight_delete_public_plan_proposal" +) // ProposalContents defines the module weighted proposals' contents func ProposalContents(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) []simtypes.WeightedProposalContent { return []simtypes.WeightedProposalContent{ simulation.NewWeightedProposalContent( - OpWeightSimulatePublicPlanProposal, - params.DefaultWeightPublicPlanProposal, - SimulatePublicPlanProposal(ak, bk, k), + OpWeightSimulateAddPublicPlanProposal, + params.DefaultWeightAddPublicPlanProposal, + SimulateAddPublicPlanProposal(ak, bk, k), + ), + simulation.NewWeightedProposalContent( + OpWeightSimulateUpdatePublicPlanProposal, + params.DefaultWeightUpdatePublicPlanProposal, + SimulateUpdatePublicPlanProposal(ak, bk, k), + ), + simulation.NewWeightedProposalContent( + OpWeightSimulateDeletePublicPlanProposal, + params.DefaultWeightDeletePublicPlanProposal, + SimulateDeletePublicPlanProposal(ak, bk, k), ), } } -// SimulatePublicPlanProposal generates random public plan proposal content -func SimulatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.ContentSimulatorFn { +// SimulateAddPublicPlanProposal generates random public plan proposal content +func SimulateAddPublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.ContentSimulatorFn { return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { simAccount, _ := simtypes.RandomAcc(r, accs) @@ -45,8 +69,7 @@ func SimulatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k k return nil } - // create add request proposal - // TODO: randomized values of the fields + // add request proposal req := &types.AddRequestProposal{ Name: "simulation-test-" + simtypes.RandStringOfLength(r, 5), FarmingPoolAddress: simAccount.Address.String(), @@ -59,14 +82,6 @@ func SimulatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k k } addRequests := []*types.AddRequestProposal{req} - // TODO - // create update request proposal - // updating plan can only be allowed (owner) - - // TODO - // create delete request proposal - // deleting plan can only be allowed - return types.NewPublicPlanProposal( simtypes.RandStringOfLength(r, 10), simtypes.RandStringOfLength(r, 100), @@ -76,3 +91,111 @@ func SimulatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k k ) } } + +// SimulateUpdatePublicPlanProposal generates random public plan proposal content +func SimulateUpdatePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + params := k.GetParams(ctx) + _, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + if hasNeg { + return nil + } + + poolCoins, err := mintPoolCoins(ctx, r, bk, simAccount) + if err != nil { + return nil + } + + req := &types.UpdateRequestProposal{} + + // TODO: decide which values of fields to randomize + plans := k.GetAllPlans(ctx) + for _, p := range plans { + if p.GetType() == types.PlanTypePublic { + startTime := ctx.BlockTime() + endTime := startTime.AddDate(0, 1, 0) + + switch plan := p.(type) { + case *types.FixedAmountPlan: + req.PlanId = plan.GetId() + req.Name = plan.GetName() + req.FarmingPoolAddress = plan.GetFarmingPoolAddress().String() + req.TerminationAddress = plan.GetTerminationAddress().String() + req.StakingCoinWeights = plan.GetStakingCoinWeights() + req.StartTime = &startTime + req.EndTime = &endTime + req.EpochAmount = sdk.NewCoins(sdk.NewInt64Coin(poolCoins[r.Intn(3)].Denom, int64(simtypes.RandIntBetween(r, 10_000_000, 1_000_000_000)))) + case *types.RatioPlan: + req.PlanId = plan.GetId() + req.Name = plan.GetName() + req.FarmingPoolAddress = plan.GetFarmingPoolAddress().String() + req.TerminationAddress = plan.GetTerminationAddress().String() + req.StakingCoinWeights = plan.GetStakingCoinWeights() + req.StartTime = &startTime + req.EndTime = &endTime + req.EpochRatio = sdk.NewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 10)), 1) + } + break + } + } + + if req.PlanId == 0 { + return nil + } + + updateRequests := []*types.UpdateRequestProposal{req} + + return types.NewPublicPlanProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 100), + []*types.AddRequestProposal{}, + updateRequests, + []*types.DeleteRequestProposal{}, + ) + } +} + +// SimulateDeletePublicPlanProposal generates random public plan proposal content +func SimulateDeletePublicPlanProposal(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + simAccount, _ := simtypes.RandomAcc(r, accs) + + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + + params := k.GetParams(ctx) + _, hasNeg := spendable.SafeSub(params.PrivatePlanCreationFee) + if hasNeg { + return nil + } + + req := &types.DeleteRequestProposal{} + + plans := k.GetAllPlans(ctx) + for _, p := range plans { + if p.GetType() == types.PlanTypePublic { + req.PlanId = p.GetId() + break + } + } + + if req.PlanId == 0 { + return nil + } + + deleteRequest := []*types.DeleteRequestProposal{req} + + return types.NewPublicPlanProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 100), + []*types.AddRequestProposal{}, + []*types.UpdateRequestProposal{}, + deleteRequest, + ) + } +} diff --git a/x/farming/simulation/proposals_test.go b/x/farming/simulation/proposals_test.go new file mode 100644 index 00000000..24e81bd5 --- /dev/null +++ b/x/farming/simulation/proposals_test.go @@ -0,0 +1,84 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/tendermint/farming/app/params" + "github.com/tendermint/farming/x/farming/simulation" + "github.com/tendermint/farming/x/farming/types" +) + +func TestProposalContents(t *testing.T) { + app, ctx := createTestApp(false) + + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + accounts := getTestingAccounts(t, r, app, ctx, 1) + + // execute ProposalContents function + weightedProposalContent := simulation.ProposalContents(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) + require.Len(t, weightedProposalContent, 3) + + w0 := weightedProposalContent[0] + w1 := weightedProposalContent[1] + w2 := weightedProposalContent[2] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightSimulateAddPublicPlanProposal, w0.AppParamsKey()) + require.Equal(t, params.DefaultWeightAddPublicPlanProposal, w0.DefaultWeight()) + + // tests w1 interface: + require.Equal(t, simulation.OpWeightSimulateUpdatePublicPlanProposal, w1.AppParamsKey()) + require.Equal(t, params.DefaultWeightUpdatePublicPlanProposal, w1.DefaultWeight()) + + // tests w2 interface: + require.Equal(t, simulation.OpWeightSimulateDeletePublicPlanProposal, w2.AppParamsKey()) + require.Equal(t, params.DefaultWeightDeletePublicPlanProposal, w2.DefaultWeight()) + + content0 := w0.ContentSimulatorFn()(r, ctx, accounts) + + require.Equal(t, "yNhYFmBZHe", content0.GetTitle()) + require.Equal(t, "weXhSUkMhPjMaxKlMIJMOXcnQfyzeOcbWwNbeHVIkPZBSpYuLyYggwexjxusrBqDOTtGTOWeLrQKjLxzIivHSlcxgdXhhuTSkuxK", content0.GetDescription()) + require.Equal(t, "farming", content0.ProposalRoute()) + require.Equal(t, "PublicPlan", content0.ProposalType()) + + // setup public fixed amount plan + msgPlan := &types.MsgCreateFixedAmountPlan{ + Name: "simulation", + Creator: accounts[0].Address.String(), + StakingCoinWeights: sdk.NewDecCoins( + sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% + ), + StartTime: mustParseRFC3339("2021-08-01T00:00:00Z"), + EndTime: mustParseRFC3339("2021-08-31T00:00:00Z"), + EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), + } + + _, err := app.FarmingKeeper.CreateFixedAmountPlan( + ctx, + msgPlan, + accounts[0].Address, + accounts[0].Address, + types.PlanTypePublic, + ) + require.NoError(t, err) + + content1 := w1.ContentSimulatorFn()(r, ctx, accounts) + require.Equal(t, "GNoFBIHxvi", content1.GetTitle()) + require.Equal(t, "TVpQoottZyPFfNOoMioXHRuFwMRYUiKvcWPkrayyTLOCFJlAyslDameIuqVAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFR", content1.GetDescription()) + require.Equal(t, "farming", content1.ProposalRoute()) + require.Equal(t, "PublicPlan", content1.ProposalType()) + + content2 := w2.ContentSimulatorFn()(r, ctx, accounts) + require.Equal(t, "MhptXaxIxg", content2.GetTitle()) + require.Equal(t, "MBcObErwgTDNGWnwQMUgFFSKtPDMEoEQCTKVREqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvTyhXKLyhUScOXvYthRX", content2.GetDescription()) + require.Equal(t, "farming", content2.ProposalRoute()) + require.Equal(t, "PublicPlan", content2.ProposalType()) + +} diff --git a/x/farming/types/proposal.go b/x/farming/types/proposal.go index 7f00fd04..63f4fa36 100644 --- a/x/farming/types/proposal.go +++ b/x/farming/types/proposal.go @@ -26,7 +26,7 @@ func NewPublicPlanProposal( addReq []*AddRequestProposal, updateReq []*UpdateRequestProposal, deleteReq []*DeleteRequestProposal, -) gov.Content { +) *PublicPlanProposal { return &PublicPlanProposal{ Title: title, Description: description, From ef563e993dbce0e9dc444890a42aae11285b5c1f Mon Sep 17 00:00:00 2001 From: kogisin Date: Fri, 10 Sep 2021 11:32:54 +0900 Subject: [PATCH 24/31] chore: debugging export and import simulation --- app/sim_test.go | 2 +- x/farming/keeper/genesis.go | 20 +++++++++++++++--- x/farming/types/plan_test.go | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 79ea2ea5..fef71fcd 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -151,7 +151,7 @@ func TestAppImportExport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewFarmingApp(logger, newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + newApp := NewFarmingApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) require.Equal(t, appName, app.Name()) var genesisState GenesisState diff --git a/x/farming/keeper/genesis.go b/x/farming/keeper/genesis.go index 6d002f33..9bdbe1f6 100644 --- a/x/farming/keeper/genesis.go +++ b/x/farming/keeper/genesis.go @@ -9,6 +9,7 @@ import ( // InitGenesis initializes the farming module's state from a given genesis state. func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { ctx, applyCache := ctx.CacheContext() + k.SetParams(ctx, genState.Params) moduleAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleName) k.accountKeeper.SetModuleAccount(ctx, moduleAcc) @@ -26,31 +27,35 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { k.SetPlan(ctx, plan) k.SetGlobalPlanId(ctx, plan.GetId()) } + for _, staking := range genState.Stakings { k.SetStaking(ctx, staking) k.SetStakingIndex(ctx, staking) } + for _, reward := range genState.Rewards { k.SetReward(ctx, reward.StakingCoinDenom, reward.GetFarmer(), reward.RewardCoins) } + if err := k.ValidateRemainingRewardsAmount(ctx); err != nil { panic(err) } + if err := k.ValidateStakingReservedAmount(ctx); err != nil { panic(err) } + applyCache() } // ExportGenesis returns the farming module's genesis state. func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { params := k.GetParams(ctx) - var planRecords []types.PlanRecord - plans := k.GetAllPlans(ctx) stakings := k.GetAllStakings(ctx) rewards := k.GetAllRewards(ctx) + var planRecords []types.PlanRecord for _, plan := range plans { any, err := types.PackPlan(plan) if err != nil { @@ -63,5 +68,14 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { } epochTime, _ := k.GetLastEpochTime(ctx) - return types.NewGenesisState(params, planRecords, stakings, rewards, k.bankKeeper.GetAllBalances(ctx, types.StakingReserveAcc), k.bankKeeper.GetAllBalances(ctx, types.RewardsReserveAcc), epochTime) + + return types.NewGenesisState( + params, + planRecords, + stakings, + rewards, + k.bankKeeper.GetAllBalances(ctx, types.StakingReserveAcc), + k.bankKeeper.GetAllBalances(ctx, types.RewardsReserveAcc), + epochTime, + ) } diff --git a/x/farming/types/plan_test.go b/x/farming/types/plan_test.go index 72a57626..0f26525a 100644 --- a/x/farming/types/plan_test.go +++ b/x/farming/types/plan_test.go @@ -121,3 +121,42 @@ func TestPrivatePlanFarmingPoolAddress(t *testing.T) { require.Equal(t, testAcc2, sdk.AccAddress(address.Module(types.ModuleName, []byte("PrivatePlan|1|test2")))) require.Equal(t, "cosmos172yhzhxwgwul3s8m6qpgw2ww3auedq4k3dt224543d0sd44fgx4spcjthr", testAcc2.String()) } + +// TODO: needs to cover more cases +// https://github.com/tendermint/farming/issues/90 +func TestUnpackPlan(t *testing.T) { + plan := []types.PlanI{ + types.NewRatioPlan( + types.NewBasePlan( + 1, + "testPlan1", + types.PlanTypePrivate, + types.PrivatePlanFarmingPoolAddress("farmingPoolAddr1", 1).String(), + sdk.AccAddress("terminationAddr1").String(), + sdk.NewDecCoins(sdk.DecCoin{Denom: "testFarmStakingCoinDenom", Amount: sdk.MustNewDecFromStr("1.0")}), + mustParseRFC3339("2021-08-03T00:00:00Z"), + mustParseRFC3339("2021-08-07T00:00:00Z"), + ), + sdk.NewDec(1), + ), + } + + any, err := types.PackPlan(plan[0]) + require.NoError(t, err) + + planRecord := types.PlanRecord{ + Plan: *any, + FarmingPoolCoins: sdk.NewCoins(), + } + + _, err = types.UnpackPlan(&planRecord.Plan) + require.NoError(t, err) +} + +func mustParseRFC3339(s string) time.Time { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(err) + } + return t +} From 279e27638099a4586ce34b6a1055d4b76504f6f0 Mon Sep 17 00:00:00 2001 From: dongsam Date: Mon, 13 Sep 2021 19:50:31 +0900 Subject: [PATCH 25/31] fix: update simulation logics for f1 spec --- x/farming/keeper/keeper_test.go | 34 +++++++------- x/farming/keeper/staking.go | 18 ++++++++ x/farming/keeper/staking_test.go | 12 ++--- x/farming/simulation/decoder.go | 6 +++ x/farming/simulation/decoder_test.go | 13 ++---- x/farming/simulation/genesis.go | 12 ----- x/farming/simulation/genesis_test.go | 4 +- x/farming/simulation/operations.go | 59 ++++++++++++++----------- x/farming/simulation/operations_test.go | 39 +++++++++------- x/farming/simulation/params.go | 9 ---- x/farming/simulation/params_test.go | 5 +-- x/farming/spec/03_state_transitions.md | 1 - x/farming/spec/08_params.md | 5 --- x/farming/types/expected_keepers.go | 6 +++ 14 files changed, 115 insertions(+), 108 deletions(-) diff --git a/x/farming/keeper/keeper_test.go b/x/farming/keeper/keeper_test.go index fd9d3d86..f6f89ace 100644 --- a/x/farming/keeper/keeper_test.go +++ b/x/farming/keeper/keeper_test.go @@ -133,23 +133,23 @@ func (suite *KeeperTestSuite) Stake(farmerAcc sdk.AccAddress, amt sdk.Coins) { suite.Require().NoError(err) } -func (suite *KeeperTestSuite) StakedCoins(farmerAcc sdk.AccAddress) sdk.Coins { - stakedCoins := sdk.NewCoins() - suite.keeper.IterateStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, staking types.Staking) (stop bool) { - stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, staking.Amount)) - return false - }) - return stakedCoins -} - -func (suite *KeeperTestSuite) QueuedCoins(farmerAcc sdk.AccAddress) sdk.Coins { - queuedCoins := sdk.NewCoins() - suite.keeper.IterateQueuedStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, queuedStaking types.QueuedStaking) (stop bool) { - queuedCoins = queuedCoins.Add(sdk.NewCoin(stakingCoinDenom, queuedStaking.Amount)) - return false - }) - return queuedCoins -} +//func (suite *KeeperTestSuite) StakedCoins(farmerAcc sdk.AccAddress) sdk.Coins { +// stakedCoins := sdk.NewCoins() +// suite.keeper.IterateStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, staking types.Staking) (stop bool) { +// stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, staking.Amount)) +// return false +// }) +// return stakedCoins +//} +// +//func (suite *KeeperTestSuite) QueuedCoins(farmerAcc sdk.AccAddress) sdk.Coins { +// queuedCoins := sdk.NewCoins() +// suite.keeper.IterateQueuedStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, queuedStaking types.QueuedStaking) (stop bool) { +// queuedCoins = queuedCoins.Add(sdk.NewCoin(stakingCoinDenom, queuedStaking.Amount)) +// return false +// }) +// return queuedCoins +//} func (suite *KeeperTestSuite) Rewards(farmerAcc sdk.AccAddress) sdk.Coins { cacheCtx, _ := suite.ctx.CacheContext() diff --git a/x/farming/keeper/staking.go b/x/farming/keeper/staking.go index 4d4100f8..b983b110 100644 --- a/x/farming/keeper/staking.go +++ b/x/farming/keeper/staking.go @@ -60,6 +60,15 @@ func (k Keeper) IterateStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddres } } +func (k Keeper) GetAllStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { + stakedCoins := sdk.NewCoins() + k.IterateStakingsByFarmer(ctx, farmerAcc, func(stakingCoinDenom string, staking types.Staking) (stop bool) { + stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, staking.Amount)) + return false + }) + return stakedCoins +} + func (k Keeper) GetQueuedStaking(ctx sdk.Context, stakingCoinDenom string, farmerAcc sdk.AccAddress) (queuedStaking types.QueuedStaking, found bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetQueuedStakingKey(stakingCoinDenom, farmerAcc)) @@ -71,6 +80,15 @@ func (k Keeper) GetQueuedStaking(ctx sdk.Context, stakingCoinDenom string, farme return } +func (k Keeper) GetAllQueuedStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { + stakedCoins := sdk.NewCoins() + k.IterateQueuedStakingsByFarmer(ctx, farmerAcc, func(stakingCoinDenom string, queuedStaking types.QueuedStaking) (stop bool) { + stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, queuedStaking.Amount)) + return false + }) + return stakedCoins +} + func (k Keeper) SetQueuedStaking(ctx sdk.Context, stakingCoinDenom string, farmerAcc sdk.AccAddress, queuedStaking types.QueuedStaking) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshal(&queuedStaking) diff --git a/x/farming/keeper/staking_test.go b/x/farming/keeper/staking_test.go index ea2be63c..8131abeb 100644 --- a/x/farming/keeper/staking_test.go +++ b/x/farming/keeper/staking_test.go @@ -120,8 +120,8 @@ func (suite *KeeperTestSuite) TestUnstake() { suite.Error(err) } else { if suite.NoError(err) { - suite.True(coinsEq(tc.remainingStaked, suite.StakedCoins(suite.addrs[tc.addrIdx]))) - suite.True(coinsEq(tc.remainingQueued, suite.QueuedCoins(suite.addrs[tc.addrIdx]))) + suite.True(coinsEq(tc.remainingStaked, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) + suite.True(coinsEq(tc.remainingQueued, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) } } }) @@ -154,15 +154,15 @@ func (suite *KeeperTestSuite) TestProcessQueuedCoins() { } } - suite.Require().True(coinsEq(queuedCoins, suite.QueuedCoins(suite.addrs[0]))) - suite.Require().True(coinsEq(stakedCoins, suite.StakedCoins(suite.addrs[0]))) + suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[0]))) suite.keeper.ProcessQueuedCoins(suite.ctx) stakedCoins = stakedCoins.Add(queuedCoins...) queuedCoins = sdk.NewCoins() - suite.Require().True(coinsEq(queuedCoins, suite.QueuedCoins(suite.addrs[0]))) - suite.Require().True(coinsEq(stakedCoins, suite.StakedCoins(suite.addrs[0]))) + suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[0]))) } } } diff --git a/x/farming/simulation/decoder.go b/x/farming/simulation/decoder.go index b9299740..813fd93f 100644 --- a/x/farming/simulation/decoder.go +++ b/x/farming/simulation/decoder.go @@ -27,6 +27,12 @@ func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { cdc.MustUnmarshal(kvA.Value, &sB) return fmt.Sprintf("%v\n%v", sA, sB) + case bytes.Equal(kvA.Key[:1], types.QueuedStakingKeyPrefix): + var sA, sB types.QueuedStaking + cdc.MustUnmarshal(kvA.Value, &sA) + cdc.MustUnmarshal(kvA.Value, &sB) + return fmt.Sprintf("%v\n%v", sA, sB) + //TODO: add f1 struct default: panic(fmt.Sprintf("invalid farming key prefix %X", kvA.Key[:1])) diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index 68e35648..5249d5cc 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -26,16 +26,14 @@ func TestDecodeFarmingStore(t *testing.T) { basePlan := types.BasePlan{} staking := types.Staking{} - reward := types.Reward{} + queuedStaking := types.QueuedStaking{} kvPairs := kv.Pairs{ Pairs: []kv.Pair{ {Key: types.PlanKeyPrefix, Value: cdc.MustMarshal(&basePlan)}, {Key: types.StakingKeyPrefix, Value: cdc.MustMarshal(&staking)}, - {Key: types.RewardKeyPrefix, Value: cdc.MustMarshal(&reward)}, - {Key: types.PlansByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, - {Key: types.StakingByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, - {Key: types.RewardsByFarmerIndexKeyPrefix, Value: farmerAddr1.Bytes()}, + {Key: types.QueuedStakingKeyPrefix, Value: cdc.MustMarshal(&queuedStaking)}, + // TODO: f1 structs, indexes {Key: []byte{0x99}, Value: []byte{0x99}}, }, } @@ -46,10 +44,7 @@ func TestDecodeFarmingStore(t *testing.T) { }{ {"Plan", fmt.Sprintf("%v\n%v", basePlan, basePlan)}, {"Staking", fmt.Sprintf("%v\n%v", staking, staking)}, - {"Reward", fmt.Sprintf("%v\n%v", reward, reward)}, - {"PlansByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, - {"StakingByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, - {"RewardsByFarmerIndex", fmt.Sprintf("%v\n%v", farmerAddr1, farmerAddr1)}, + {"QueuedStaking", fmt.Sprintf("%v\n%v", queuedStaking, queuedStaking)}, {"other", ""}, } for i, tt := range tests { diff --git a/x/farming/simulation/genesis.go b/x/farming/simulation/genesis.go index 9a5abbbf..ac5b9fdf 100644 --- a/x/farming/simulation/genesis.go +++ b/x/farming/simulation/genesis.go @@ -17,7 +17,6 @@ import ( // Simulation parameter constants. const ( PrivatePlanCreationFee = "private_plan_creation_fee" - StakingCreationFee = "staking_creation_fee" EpochDays = "epoch_days" FarmingFeeCollector = "farming_fee_collector" ) @@ -27,11 +26,6 @@ func GenPrivatePlanCreationFee(r *rand.Rand) sdk.Coins { return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 100_000_000)))) } -// GenStakingCreationFee return randomized staking creation fee. -func GenStakingCreationFee(r *rand.Rand) sdk.Coins { - return sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simulation.RandIntBetween(r, 0, 100_000_000)))) -} - // GenEpochDays return default epoch days. func GenEpochDays(r *rand.Rand) uint32 { return uint32(simulation.RandIntBetween(r, int(types.DefaultEpochDays), 10)) @@ -50,12 +44,6 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { privatePlanCreationFee = GenPrivatePlanCreationFee(r) }, ) - var stakingCreationFee sdk.Coins - simState.AppParams.GetOrGenerate( - simState.Cdc, StakingCreationFee, &stakingCreationFee, simState.Rand, - func(r *rand.Rand) { stakingCreationFee = GenStakingCreationFee(r) }, - ) - var epochDays uint32 simState.AppParams.GetOrGenerate( simState.Cdc, EpochDays, &epochDays, simState.Rand, diff --git a/x/farming/simulation/genesis_test.go b/x/farming/simulation/genesis_test.go index d199def3..727d7993 100644 --- a/x/farming/simulation/genesis_test.go +++ b/x/farming/simulation/genesis_test.go @@ -41,12 +41,10 @@ func TestRandomizedGenState(t *testing.T) { simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) dec1 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(36122540))) - dec2 := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(8240456))) - dec3 := uint32(7) + dec3 := uint32(5) dec4 := "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" require.Equal(t, dec1, genState.Params.PrivatePlanCreationFee) - require.Equal(t, dec2, genState.Params.StakingCreationFee) require.Equal(t, dec3, genState.Params.EpochDays) require.Equal(t, dec4, genState.Params.FarmingFeeCollector) } diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 94341a13..21e9cb9a 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -223,12 +223,6 @@ func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 1_000_000_000))), ) - params := k.GetParams(ctx) - _, hasNeg := spendable.SafeSub(stakingCoins.Add(params.StakingCreationFee...)) - if hasNeg { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgStake, "insufficient balance for staking creation fee"), nil, nil - } - msg := types.NewMsgStake(farmer, stakingCoins) txCtx := simulation.OperationInput{ @@ -267,13 +261,25 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke ) // staking must exist in order to unstake - staking, found := k.GetStakingByFarmer(ctx, farmer) - if !found { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking"), nil, nil + staking, sf := k.GetStaking(ctx, sdk.DefaultBondDenom, farmer) + if !sf { + staking = types.Staking{ + Amount: sdk.ZeroInt(), + } + } + queuedStaking, qsf := k.GetQueuedStaking(ctx, sdk.DefaultBondDenom, farmer) + if !qsf { + if !qsf { + queuedStaking = types.QueuedStaking{ + Amount: sdk.ZeroInt(), + } + } + } + if !sf && !qsf { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "unable to find staking and queued staking"), nil, nil } - // sum of staked and queued coins must be greater than unstaking coins - if !staking.StakedCoins.Add(staking.QueuedCoins...).IsAllGTE(unstakingCoins) { + if !staking.Amount.Add(queuedStaking.Amount).GTE(unstakingCoins[0].Amount) { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), nil, nil } @@ -283,7 +289,6 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke } msg := types.NewMsgUnstake(farmer, unstakingCoins) - txCtx := simulation.OperationInput{ R: r, App: app, @@ -298,7 +303,6 @@ func SimulateMsgUnstake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke ModuleName: types.ModuleName, CoinsSpentInMsg: spendable, } - return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -317,35 +321,38 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke var simAccount simtypes.Account // find staking from the simulated accounts - var ranStaking types.Staking + var ranStaking sdk.Coins for _, acc := range accs { - if staking, found := k.GetStakingByFarmer(ctx, acc.Address); found { + staking := k.GetAllStakingsByFarmer(ctx, acc.Address) + if !staking.IsZero() { simAccount = acc ranStaking = staking break } } - // process queued coins and distribute rewards relative to the current epoch days - params := k.GetParams(ctx) - k.ProcessQueuedCoins(ctx) - ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(0, 0, int(params.EpochDays))) - for i := 0; i < int(params.EpochDays); i++ { - if err := k.DistributeRewards(ctx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to distribute rewards"), nil, nil + var stakingCoinDenoms []string + for _, coin := range ranStaking { + stakingCoinDenoms = append(stakingCoinDenoms, coin.Denom) + } + + var totalRewards sdk.Coins + for _, denom := range stakingCoinDenoms { + rewards, err := k.WithdrawRewards(ctx, simAccount.Address, denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "unable to withdraw rewards"), nil, nil } + totalRewards = totalRewards.Add(rewards...) } - k.SetLastEpochTime(ctx, ctx.BlockTime()) - rewards := k.GetRewardsByFarmer(ctx, simAccount.Address) - if len(rewards) == 0 { + if totalRewards.IsZero() { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgHarvest, "no rewards to harvest"), nil, nil } account := ak.GetAccount(ctx, simAccount.Address) spendable := bk.SpendableCoins(ctx, account.GetAddress()) - msg := types.NewMsgHarvest(simAccount.Address, ranStaking.StakingCoinDenoms()) + msg := types.NewMsgHarvest(simAccount.Address, stakingCoinDenoms) txCtx := simulation.OperationInput{ R: r, diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 7d063f3d..2545b752 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -146,9 +146,7 @@ func TestSimulateMsgStake(t *testing.T) { accounts := getTestingAccounts(t, r, app, ctx, 1) // setup randomly generated staking creation fees - feeCoins := simulation.GenStakingCreationFee(r) params := app.FarmingKeeper.GetParams(ctx) - params.StakingCreationFee = feeCoins app.FarmingKeeper.SetParams(ctx, params) // begin a new block @@ -166,7 +164,7 @@ func TestSimulateMsgStake(t *testing.T) { require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgStake, msg.Type()) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) - require.Equal(t, "476941318stake", msg.StakingCoins.String()) + require.Equal(t, "912902081stake", msg.StakingCoins.String()) require.Len(t, futureOperations, 0) } @@ -182,18 +180,17 @@ func TestSimulateMsgUnstake(t *testing.T) { accounts := getTestingAccounts(t, r, app, ctx, 1) // setup randomly generated staking creation fees - feeCoins := simulation.GenStakingCreationFee(r) params := app.FarmingKeeper.GetParams(ctx) - params.StakingCreationFee = feeCoins app.FarmingKeeper.SetParams(ctx, params) // staking must exist in order to simulate unstake stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) - _, err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) + err := app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) require.NoError(t, err) // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + app.FarmingKeeper.AdvanceEpoch(ctx) // execute operation op := simulation.SimulateMsgUnstake(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) @@ -207,7 +204,7 @@ func TestSimulateMsgUnstake(t *testing.T) { require.True(t, operationMsg.OK) require.Equal(t, types.TypeMsgUnstake, msg.Type()) require.Equal(t, "cosmos1tnh2q55v8wyygtt9srz5safamzdengsnqeycj3", msg.Farmer) - require.Equal(t, "89941318stake", msg.UnstakingCoins.String()) + require.Equal(t, "21902081stake", msg.UnstakingCoins.String()) require.Len(t, futureOperations, 0) } @@ -234,8 +231,8 @@ func TestSimulateMsgHarvest(t *testing.T) { StakingCoinWeights: sdk.NewDecCoins( sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDecWithPrec(10, 1)), // 100% ), - StartTime: mustParseRFC3339("2021-08-01T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-31T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 200_000_000)), } @@ -253,19 +250,27 @@ func TestSimulateMsgHarvest(t *testing.T) { // set staking stakingCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)) - _, err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) + err = app.FarmingKeeper.Stake(ctx, accounts[0].Address, stakingCoins) require.NoError(t, err) - app.FarmingKeeper.ProcessQueuedCoins(ctx) - ctx = ctx.WithBlockTime(mustParseRFC3339("2021-08-20T00:00:00Z")) - err = app.FarmingKeeper.DistributeRewards(ctx) - require.NoError(t, err) + queuedStaking, found := app.FarmingKeeper.GetQueuedStaking(ctx, sdk.DefaultBondDenom, accounts[0].Address) + require.Equal(t, true, found) + require.Equal(t, true, queuedStaking.Amount.IsPositive()) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + app.FarmingKeeper.AdvanceEpoch(ctx) // check that queue coins are moved to staked coins - staking, found := app.FarmingKeeper.GetStaking(ctx, uint64(1)) + staking, found := app.FarmingKeeper.GetStaking(ctx, sdk.DefaultBondDenom, accounts[0].Address) require.Equal(t, true, found) - require.Equal(t, true, staking.QueuedCoins.IsZero()) - require.Equal(t, true, staking.StakedCoins.IsAllPositive()) + require.Equal(t, true, staking.Amount.IsPositive()) + queuedStaking, found = app.FarmingKeeper.GetQueuedStaking(ctx, sdk.DefaultBondDenom, accounts[0].Address) + require.Equal(t, false, found) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + app.FarmingKeeper.AdvanceEpoch(ctx) // execute operation op := simulation.SimulateMsgHarvest(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) diff --git a/x/farming/simulation/params.go b/x/farming/simulation/params.go index 213c0cb3..e10a36a6 100644 --- a/x/farming/simulation/params.go +++ b/x/farming/simulation/params.go @@ -25,15 +25,6 @@ func ParamChanges(r *rand.Rand) []simtypes.ParamChange { return string(bz) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyStakingCreationFee), - func(r *rand.Rand) string { - bz, err := GenPrivatePlanCreationFee(r).MarshalJSON() - if err != nil { - panic(err) - } - return string(bz) - }, - ), simulation.NewSimParamChange(types.ModuleName, string(types.KeyEpochDays), func(r *rand.Rand) string { return fmt.Sprintf("%d", GenEpochDays(r)) diff --git a/x/farming/simulation/params_test.go b/x/farming/simulation/params_test.go index 3b13897c..fd93cd39 100644 --- a/x/farming/simulation/params_test.go +++ b/x/farming/simulation/params_test.go @@ -20,13 +20,12 @@ func TestParamChanges(t *testing.T) { subspace string }{ {"farming/PrivatePlanCreationFee", "PrivatePlanCreationFee", "[{\"denom\":\"stake\",\"amount\":\"98498081\"}]", "farming"}, - {"farming/StakingCreationFee", "StakingCreationFee", "[{\"denom\":\"stake\",\"amount\":\"19727887\"}]", "farming"}, - {"farming/EpochDays", "EpochDays", "3", "farming"}, + {"farming/EpochDays", "EpochDays", "7", "farming"}, {"farming/FarmingFeeCollector", "FarmingFeeCollector", "\"cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x\"", "farming"}, } paramChanges := simulation.ParamChanges(r) - require.Len(t, paramChanges, 4) + require.Len(t, paramChanges, 3) for i, p := range paramChanges { require.Equal(t, expected[i].composedKey, p.ComposedKey()) diff --git a/x/farming/spec/03_state_transitions.md b/x/farming/spec/03_state_transitions.md index 24cff93d..9eaf837b 100644 --- a/x/farming/spec/03_state_transitions.md +++ b/x/farming/spec/03_state_transitions.md @@ -42,7 +42,6 @@ const ( ## Staking - New `Staking` object is created when a farmer creates a staking, and when the farmer does not have existing `Staking`. -- When a farmer creates new staking, the farmer should pay `StakingCreationFee` to prevent spamming. - When a farmer add/remove stakings to/from existing `Staking`, `StakedCoins` and `QueuedCoins` are updated in the corresponding `Staking`. - `QueuedCoins` : newly staked coins are in this status until end of current epoch, and then migrated to `StakedCoins` at the end of current epoch. - When a farmer unstakes, `QueuedCoins` are unstaked first, and then `StakedCoins`. diff --git a/x/farming/spec/08_params.md b/x/farming/spec/08_params.md index fb50590a..e6fcf4e1 100644 --- a/x/farming/spec/08_params.md +++ b/x/farming/spec/08_params.md @@ -7,7 +7,6 @@ The farming module contains the following parameters: | Key | Type | Example | | -------------------------- | --------- | ------------------------------------------------------------------- | | PrivatePlanCreationFee | sdk.Coins | [{"denom":"stake","amount":"100000000"}] | -| StakingCreationFee | sdk.Coins | [{"denom":"stake","amount":"100000"}] | | EpochDays | uint32 | 1 | | FarmingFeeCollector | string | "cosmos1h292smhhttwy0rl3qr4p6xsvpvxc4v05s6rxtczwq3cs6qc462mqejwy8x" | @@ -15,10 +14,6 @@ The farming module contains the following parameters: Fee paid for to create a Private type Farming plan. This fee prevents spamming and is collected in in the community pool of the distribution module. -## StakingCreationFee - -When a farmer creates new `Staking`, the farmer needs to pay `StakingCreationFee` to prevent spam on the `Staking` struct. - ## EpochDays The universal epoch length in number of days. Every process for staking and reward distribution is executed with this `EpochDays` frequency. diff --git a/x/farming/types/expected_keepers.go b/x/farming/types/expected_keepers.go index 30cfbd12..5984cb5b 100644 --- a/x/farming/types/expected_keepers.go +++ b/x/farming/types/expected_keepers.go @@ -9,10 +9,16 @@ import ( type BankKeeper interface { SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + //GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error } // AccountKeeper defines the expected account keeper type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI SetModuleAccount(sdk.Context, authtypes.ModuleAccountI) From 86cb93002adfcc934ff4053306c612d68cad716e Mon Sep 17 00:00:00 2001 From: dongsam Date: Mon, 13 Sep 2021 20:13:47 +0900 Subject: [PATCH 26/31] fix: fix test plan dates and expected codes, lint --- x/farming/client/cli/cli_test.go | 50 ++++++++++++------------- x/farming/simulation/decoder_test.go | 2 - x/farming/simulation/operations_test.go | 9 +++-- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/x/farming/client/cli/cli_test.go b/x/farming/client/cli/cli_test.go index e584ae16..7efe0a90 100644 --- a/x/farming/client/cli/cli_test.go +++ b/x/farming/client/cli/cli_test.go @@ -82,8 +82,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case1 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -94,8 +94,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM`, StakingCoinWeights: sdk.NewDecCoins(), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -103,8 +103,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case3 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: sdk.NewDecCoins(), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -112,8 +112,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case4 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("poolD35A0CC16EE598F90B044CE296A405BA9C381E38837599D96F2F70C2F02A23A4", sdk.NewInt(2))), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -121,8 +121,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case5 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -130,8 +130,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case6 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 0)), } @@ -247,8 +247,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() { case1 := cli.PrivateRatioPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochRatio: sdk.MustNewDecFromStr("0.1"), } @@ -259,8 +259,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() { OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM`, StakingCoinWeights: sdk.NewDecCoins(), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochRatio: sdk.MustNewDecFromStr("0.1"), } @@ -268,8 +268,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() { case3 := cli.PrivateRatioPlanRequest{ Name: name, StakingCoinWeights: sdk.NewDecCoins(), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochRatio: sdk.MustNewDecFromStr("0.1"), } @@ -277,8 +277,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() { case4 := cli.PrivateRatioPlanRequest{ Name: name, StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("poolD35A0CC16EE598F90B044CE296A405BA9C381E38837599D96F2F70C2F02A23A4", sdk.NewInt(2))), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochRatio: sdk.MustNewDecFromStr("0.1"), } @@ -295,8 +295,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() { case6 := cli.PrivateRatioPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochRatio: sdk.MustNewDecFromStr("1.1"), } @@ -523,8 +523,8 @@ func (s *IntegrationTestSuite) TestNewHarvestCmd() { req := cli.PrivateFixedPlanRequest{ Name: "test", StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("stake", sdk.NewInt(1))), - StartTime: mustParseRFC3339("2021-08-06T00:00:00Z"), - EndTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), + EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("node0token", 100_000_000)), } @@ -563,7 +563,7 @@ func (s *IntegrationTestSuite) TestNewHarvestCmd() { fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - false, &sdk.TxResponse{}, 6, + false, &sdk.TxResponse{}, 1, }, { "invalid staking coin denoms case #1", diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index 5249d5cc..8dbab1b9 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/simapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" "github.com/tendermint/farming/x/farming/simulation" @@ -17,7 +16,6 @@ import ( var ( pk1 = ed25519.GenPrivKey().PubKey() - farmerAddr1 = sdk.AccAddress(pk1.Address()) ) func TestDecodeFarmingStore(t *testing.T) { diff --git a/x/farming/simulation/operations_test.go b/x/farming/simulation/operations_test.go index 2545b752..54e2015e 100644 --- a/x/farming/simulation/operations_test.go +++ b/x/farming/simulation/operations_test.go @@ -190,7 +190,8 @@ func TestSimulateMsgUnstake(t *testing.T) { // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - app.FarmingKeeper.AdvanceEpoch(ctx) + err = app.FarmingKeeper.AdvanceEpoch(ctx) + require.NoError(t, err) // execute operation op := simulation.SimulateMsgUnstake(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) @@ -259,7 +260,8 @@ func TestSimulateMsgHarvest(t *testing.T) { // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - app.FarmingKeeper.AdvanceEpoch(ctx) + err = app.FarmingKeeper.AdvanceEpoch(ctx) + require.NoError(t, err) // check that queue coins are moved to staked coins staking, found := app.FarmingKeeper.GetStaking(ctx, sdk.DefaultBondDenom, accounts[0].Address) @@ -270,7 +272,8 @@ func TestSimulateMsgHarvest(t *testing.T) { // begin a new block app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - app.FarmingKeeper.AdvanceEpoch(ctx) + err = app.FarmingKeeper.AdvanceEpoch(ctx) + require.NoError(t, err) // execute operation op := simulation.SimulateMsgHarvest(app.AccountKeeper, app.BankKeeper, app.FarmingKeeper) From a459eaf0e07225a44388c619fcfee4a80314bc24 Mon Sep 17 00:00:00 2001 From: dongsam Date: Mon, 13 Sep 2021 20:35:12 +0900 Subject: [PATCH 27/31] fix: lint and validation logic of simulation --- x/farming/client/cli/cli_test.go | 2 +- x/farming/simulation/decoder_test.go | 5 ----- x/farming/simulation/operations.go | 4 +++- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/x/farming/client/cli/cli_test.go b/x/farming/client/cli/cli_test.go index 7efe0a90..239f89fc 100644 --- a/x/farming/client/cli/cli_test.go +++ b/x/farming/client/cli/cli_test.go @@ -195,7 +195,7 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - true, &sdk.TxResponse{}, 0, + true, &sdk.TxResponse{}, 1, }, { "invalid epoch amount case #1", diff --git a/x/farming/simulation/decoder_test.go b/x/farming/simulation/decoder_test.go index 8dbab1b9..c58ae880 100644 --- a/x/farming/simulation/decoder_test.go +++ b/x/farming/simulation/decoder_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/types/kv" @@ -14,10 +13,6 @@ import ( "github.com/tendermint/farming/x/farming/types" ) -var ( - pk1 = ed25519.GenPrivKey().PubKey() -) - func TestDecodeFarmingStore(t *testing.T) { cdc := simapp.MakeTestEncodingConfig().Marshaler dec := simulation.NewDecodeStore(cdc) diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 21e9cb9a..55d51636 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -222,9 +222,11 @@ func SimulateMsgStake(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep stakingCoins := sdk.NewCoins( sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 1_000_000, 1_000_000_000))), ) + if !spendable.IsAllGTE(stakingCoins) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUnstake, "insufficient funds"), nil, nil + } msg := types.NewMsgStake(farmer, stakingCoins) - txCtx := simulation.OperationInput{ R: r, App: app, From c20750e23046879d76b88603c4e39fc422d82eb5 Mon Sep 17 00:00:00 2001 From: dongsam Date: Mon, 13 Sep 2021 21:23:04 +0900 Subject: [PATCH 28/31] fix: revert test plan and add validation logic for unstake --- x/farming/client/cli/cli_test.go | 6 ++++-- x/farming/keeper/staking.go | 26 +++++++++++++++----------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/x/farming/client/cli/cli_test.go b/x/farming/client/cli/cli_test.go index 239f89fc..fe940a7b 100644 --- a/x/farming/client/cli/cli_test.go +++ b/x/farming/client/cli/cli_test.go @@ -121,8 +121,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() { case5 := cli.PrivateFixedPlanRequest{ Name: name, StakingCoinWeights: coinWeights, - StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"), - EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"), + StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"), + EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"), EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)), } @@ -510,6 +510,8 @@ func (s *IntegrationTestSuite) TestNewUnstakeCmd() { s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) txResp := tc.respType.(*sdk.TxResponse) + fmt.Println(txResp) + fmt.Println(out.String()) s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) } }) diff --git a/x/farming/keeper/staking.go b/x/farming/keeper/staking.go index b983b110..82f82f57 100644 --- a/x/farming/keeper/staking.go +++ b/x/farming/keeper/staking.go @@ -209,8 +209,10 @@ func (k Keeper) Unstake(ctx sdk.Context, farmerAcc sdk.AccAddress, amount sdk.Co sdkerrors.ErrInsufficientFunds, "%s%s is smaller than %s%s", availableAmt, coin.Denom, coin.Amount, coin.Denom) } - if _, err := k.WithdrawRewards(ctx, farmerAcc, coin.Denom); err != nil { - return err + if staking.Amount.IsPositive() { + if _, err := k.WithdrawRewards(ctx, farmerAcc, coin.Denom); err != nil { + return err + } } removedFromStaking := sdk.ZeroInt() @@ -220,14 +222,13 @@ func (k Keeper) Unstake(ctx sdk.Context, farmerAcc sdk.AccAddress, amount sdk.Co staking.Amount = staking.Amount.Add(queuedStaking.Amount) removedFromStaking = queuedStaking.Amount.Neg() queuedStaking.Amount = sdk.ZeroInt() - } - - if staking.Amount.IsPositive() { - currentEpoch := k.GetCurrentEpoch(ctx, coin.Denom) - staking.StartingEpoch = currentEpoch - k.SetStaking(ctx, coin.Denom, farmerAcc, staking) - } else { - k.DeleteStaking(ctx, coin.Denom, farmerAcc) + if staking.Amount.IsPositive() { + currentEpoch := k.GetCurrentEpoch(ctx, coin.Denom) + staking.StartingEpoch = currentEpoch + k.SetStaking(ctx, coin.Denom, farmerAcc, staking) + } else { + k.DeleteStaking(ctx, coin.Denom, farmerAcc) + } } if queuedStaking.Amount.IsPositive() { @@ -236,7 +237,10 @@ func (k Keeper) Unstake(ctx sdk.Context, farmerAcc sdk.AccAddress, amount sdk.Co k.DeleteQueuedStaking(ctx, coin.Denom, farmerAcc) } - totalStaking, _ := k.GetTotalStaking(ctx, coin.Denom) + totalStaking, found := k.GetTotalStaking(ctx, coin.Denom) + if !found { + totalStaking.Amount = sdk.ZeroInt() + } totalStaking.Amount = totalStaking.Amount.Sub(removedFromStaking) k.SetTotalStaking(ctx, coin.Denom, totalStaking) } From 043807f9167fb14f9c5187417eb5529c815cfe3b Mon Sep 17 00:00:00 2001 From: dongsam Date: Mon, 13 Sep 2021 21:29:41 +0900 Subject: [PATCH 29/31] chore: remove unused test functions --- x/farming/keeper/keeper_test.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/x/farming/keeper/keeper_test.go b/x/farming/keeper/keeper_test.go index f6f89ace..6e057fb4 100644 --- a/x/farming/keeper/keeper_test.go +++ b/x/farming/keeper/keeper_test.go @@ -133,24 +133,6 @@ func (suite *KeeperTestSuite) Stake(farmerAcc sdk.AccAddress, amt sdk.Coins) { suite.Require().NoError(err) } -//func (suite *KeeperTestSuite) StakedCoins(farmerAcc sdk.AccAddress) sdk.Coins { -// stakedCoins := sdk.NewCoins() -// suite.keeper.IterateStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, staking types.Staking) (stop bool) { -// stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, staking.Amount)) -// return false -// }) -// return stakedCoins -//} -// -//func (suite *KeeperTestSuite) QueuedCoins(farmerAcc sdk.AccAddress) sdk.Coins { -// queuedCoins := sdk.NewCoins() -// suite.keeper.IterateQueuedStakingsByFarmer(suite.ctx, farmerAcc, func(stakingCoinDenom string, queuedStaking types.QueuedStaking) (stop bool) { -// queuedCoins = queuedCoins.Add(sdk.NewCoin(stakingCoinDenom, queuedStaking.Amount)) -// return false -// }) -// return queuedCoins -//} - func (suite *KeeperTestSuite) Rewards(farmerAcc sdk.AccAddress) sdk.Coins { cacheCtx, _ := suite.ctx.CacheContext() rewards, err := suite.keeper.WithdrawAllRewards(cacheCtx, farmerAcc) From af63cacb88b188cdf126b9546ffe976336400bf5 Mon Sep 17 00:00:00 2001 From: dongsam Date: Tue, 14 Sep 2021 15:30:23 +0900 Subject: [PATCH 30/31] fix: apply suggested reviews --- x/farming/keeper/proposal_handler_test.go | 4 ++-- x/farming/keeper/staking.go | 4 ++-- x/farming/keeper/staking_test.go | 12 ++++++------ x/farming/simulation/operations.go | 2 +- x/farming/types/plan.go | 4 ++-- x/farming/types/plan_test.go | 10 ++++++++++ 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/x/farming/keeper/proposal_handler_test.go b/x/farming/keeper/proposal_handler_test.go index 165e7e5d..67766257 100644 --- a/x/farming/keeper/proposal_handler_test.go +++ b/x/farming/keeper/proposal_handler_test.go @@ -1,14 +1,14 @@ package keeper_test import ( - _ "github.com/stretchr/testify/suite" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/tendermint/farming/app" "github.com/tendermint/farming/x/farming/keeper" "github.com/tendermint/farming/x/farming/types" + + _ "github.com/stretchr/testify/suite" ) func (suite *KeeperTestSuite) TestAddPublicPlanProposal() { diff --git a/x/farming/keeper/staking.go b/x/farming/keeper/staking.go index 82f82f57..8d097af2 100644 --- a/x/farming/keeper/staking.go +++ b/x/farming/keeper/staking.go @@ -60,7 +60,7 @@ func (k Keeper) IterateStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddres } } -func (k Keeper) GetAllStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { +func (k Keeper) GetAllStakedCoinsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { stakedCoins := sdk.NewCoins() k.IterateStakingsByFarmer(ctx, farmerAcc, func(stakingCoinDenom string, staking types.Staking) (stop bool) { stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, staking.Amount)) @@ -80,7 +80,7 @@ func (k Keeper) GetQueuedStaking(ctx sdk.Context, stakingCoinDenom string, farme return } -func (k Keeper) GetAllQueuedStakingsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { +func (k Keeper) GetAllQueuedStakedCoinsByFarmer(ctx sdk.Context, farmerAcc sdk.AccAddress) sdk.Coins { stakedCoins := sdk.NewCoins() k.IterateQueuedStakingsByFarmer(ctx, farmerAcc, func(stakingCoinDenom string, queuedStaking types.QueuedStaking) (stop bool) { stakedCoins = stakedCoins.Add(sdk.NewCoin(stakingCoinDenom, queuedStaking.Amount)) diff --git a/x/farming/keeper/staking_test.go b/x/farming/keeper/staking_test.go index 8131abeb..fe07ee40 100644 --- a/x/farming/keeper/staking_test.go +++ b/x/farming/keeper/staking_test.go @@ -120,8 +120,8 @@ func (suite *KeeperTestSuite) TestUnstake() { suite.Error(err) } else { if suite.NoError(err) { - suite.True(coinsEq(tc.remainingStaked, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) - suite.True(coinsEq(tc.remainingQueued, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) + suite.True(coinsEq(tc.remainingStaked, suite.keeper.GetAllStakedCoinsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) + suite.True(coinsEq(tc.remainingQueued, suite.keeper.GetAllQueuedStakedCoinsByFarmer(suite.ctx, suite.addrs[tc.addrIdx]))) } } }) @@ -154,15 +154,15 @@ func (suite *KeeperTestSuite) TestProcessQueuedCoins() { } } - suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[0]))) - suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakedCoinsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakedCoinsByFarmer(suite.ctx, suite.addrs[0]))) suite.keeper.ProcessQueuedCoins(suite.ctx) stakedCoins = stakedCoins.Add(queuedCoins...) queuedCoins = sdk.NewCoins() - suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakingsByFarmer(suite.ctx, suite.addrs[0]))) - suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakingsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(queuedCoins, suite.keeper.GetAllQueuedStakedCoinsByFarmer(suite.ctx, suite.addrs[0]))) + suite.Require().True(coinsEq(stakedCoins, suite.keeper.GetAllStakedCoinsByFarmer(suite.ctx, suite.addrs[0]))) } } } diff --git a/x/farming/simulation/operations.go b/x/farming/simulation/operations.go index 55d51636..2d7ff762 100644 --- a/x/farming/simulation/operations.go +++ b/x/farming/simulation/operations.go @@ -325,7 +325,7 @@ func SimulateMsgHarvest(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Ke // find staking from the simulated accounts var ranStaking sdk.Coins for _, acc := range accs { - staking := k.GetAllStakingsByFarmer(ctx, acc.Address) + staking := k.GetAllStakedCoinsByFarmer(ctx, acc.Address) if !staking.IsZero() { simAccount = acc ranStaking = staking diff --git a/x/farming/types/plan.go b/x/farming/types/plan.go index 35be3375..08a72a94 100644 --- a/x/farming/types/plan.go +++ b/x/farming/types/plan.go @@ -256,13 +256,13 @@ func ValidateName(i interface{}) error { return sdkerrors.Wrapf(ErrInvalidPlanType, "invalid plan type %T", i) } - names := make(map[string]bool) + names := map[string]struct{}{} for _, plan := range plans { if _, ok := names[plan.GetName()]; ok { return sdkerrors.Wrap(ErrDuplicatePlanName, plan.GetName()) } - names[plan.GetName()] = true + names[plan.GetName()] = struct{}{} } return nil diff --git a/x/farming/types/plan_test.go b/x/farming/types/plan_test.go index 0f26525a..c53e01a7 100644 --- a/x/farming/types/plan_test.go +++ b/x/farming/types/plan_test.go @@ -144,6 +144,16 @@ func TestUnpackPlan(t *testing.T) { any, err := types.PackPlan(plan[0]) require.NoError(t, err) + marshaled, err := any.Marshal() + require.NoError(t, err) + + any.Value = []byte{} + err = any.Unmarshal(marshaled) + require.NoError(t, err) + + reMarshal, err := any.Marshal() + require.Equal(t, marshaled, reMarshal) + planRecord := types.PlanRecord{ Plan: *any, FarmingPoolCoins: sdk.NewCoins(), From f6222e33796a4ef424fc8712c74cbb25d2336a51 Mon Sep 17 00:00:00 2001 From: dongsam Date: Tue, 14 Sep 2021 16:16:47 +0900 Subject: [PATCH 31/31] chore: fix lint --- x/farming/types/plan_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/farming/types/plan_test.go b/x/farming/types/plan_test.go index c53e01a7..e6681c16 100644 --- a/x/farming/types/plan_test.go +++ b/x/farming/types/plan_test.go @@ -152,6 +152,7 @@ func TestUnpackPlan(t *testing.T) { require.NoError(t, err) reMarshal, err := any.Marshal() + require.NoError(t, err) require.Equal(t, marshaled, reMarshal) planRecord := types.PlanRecord{