Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add genesis functionality #87

Merged
merged 4 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
dbm "github.com/tendermint/tm-db"
)

func TestSimAppExportAndBlockedAddrs(t *testing.T) {
func TestSimAppExport(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{})
Expand Down
2 changes: 1 addition & 1 deletion app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (app *FarmingApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr
counter := int16(0)

for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(iter.Key()[1:])
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
validator, found := app.StakingKeeper.GetValidator(ctx, addr)
if !found {
panic("expected validator, not found")
Expand Down
47 changes: 25 additions & 22 deletions proto/tendermint/farming/v1beta1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ message GenesisState {

// rewards defines the reward records used for genesis state
repeated Reward rewards = 4 [(gogoproto.nullable) = false];

// staking_reserve_coins specifies balance of the staking reserve pool staked in the plans
// this param is needed for import/export validation
repeated cosmos.base.v1beta1.Coin staking_reserve_coins = 5 [
(gogoproto.moretags) = "yaml:\"staking_reserve_coins\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];

// reward_pool_coins specifies balance of the reward pool to be distributed in the plans
// this param is needed for import/export validation
repeated cosmos.base.v1beta1.Coin reward_pool_coins = 6 [
(gogoproto.moretags) = "yaml:\"reward_pool_coins\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];

// global_last_epoch_time specifies the last executed epoch time of the plans
google.protobuf.Timestamp global_last_epoch_time = 7 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"global_last_epoch_time\""
];
}

// PlanRecord is used for import/export via genesis json.
Expand All @@ -36,33 +59,13 @@ message PlanRecord {
option (gogoproto.goproto_getters) = false;

// plan specifies the plan interface; it can be FixedAmountPlan or RatioPlan
google.protobuf.Any plan = 1 [(gogoproto.nullable) = false];

// last_epoch_time specifies the last distributed epoch time of the plan
google.protobuf.Timestamp last_epoch_time = 2
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"last_epoch_time\""];
google.protobuf.Any plan = 1 [(gogoproto.nullable) = false, (cosmos_proto.accepts_interface) = "PlanI"];

// farming_pool_coins specifies balance of the farming pool for the plan
// this param is needed for import/export validation
repeated cosmos.base.v1beta1.Coin farming_pool_coins = 3 [
repeated cosmos.base.v1beta1.Coin farming_pool_coins = 2 [
(gogoproto.moretags) = "yaml:\"farming_pool_coins\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];

// reward_pool_coins specifies balance of the reward pool to be distributed in
// the plan this param is needed for import/export validation
repeated cosmos.base.v1beta1.Coin reward_pool_coins = 4 [
(gogoproto.moretags) = "yaml:\"reward_pool_coins\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];

// staking_reserve_coins specifies balance of the staking reserve pool staked
// in the plan this param is needed for import/export validation
repeated cosmos.base.v1beta1.Coin staking_reserve_coins = 5 [
(gogoproto.moretags) = "yaml:\"staking_reserve_coins\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}
100 changes: 42 additions & 58 deletions x/farming/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,60 @@ import (

// InitGenesis initializes the farming module's state from a given genesis state.
func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) {
if err := k.ValidateGenesis(ctx, genState); err != nil {
panic(err)
}

ctx, applyCache := ctx.CacheContext()
k.SetParams(ctx, genState.Params)
moduleAcc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
k.accountKeeper.SetModuleAccount(ctx, moduleAcc)

// TODO: decision making is needed
// feeCollectorAcc, err := sdk.AccAddressFromBech32(types.DefaultFarmingFeeCollector)
// if err != nil {
// panic(err)
// }

// k.accountKeeper.SetModuleAccount(ctx, feeCollectorAcc)

// TODO: unimplemented
//for _, record := range genState.PlanRecords {
// k.SetPlanRecord(ctx, record)
//}
//for _, staking := range genState.Stakings {
// k.SetStaking(ctx, staking)
//}
//for _, reward := range genState.Rewards {
// k.SetReward(ctx, reword)
//}
_, err := sdk.AccAddressFromBech32(genState.Params.FarmingFeeCollector)
if err != nil {
panic(err)
}

for _, record := range genState.PlanRecords {
plan, err := types.UnpackPlan(&record.Plan)
if err != nil {
panic(err)
}
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)

// TODO: unimplemented
var planRecords []types.PlanRecord

//plans := k.GetAllPlans(ctx)
//stakings := k.GetAllStakings(ctx)
//rewards := k.GetAllRewards(ctx)

//for _, plan := range plans {
// record, found := k.GetPlanRecord(ctx, plan)
// if found {
// planRecords = append(planRecords, record)
// }
//}
//
//if len(planRecords) == 0 {
// planRecords = []types.PlanRecord{}
//}

return types.NewGenesisState(params, planRecords, nil, nil)
}

// ValidateGenesis validates the farming module's genesis state.
func (k Keeper) ValidateGenesis(ctx sdk.Context, genState types.GenesisState) error {
if err := genState.Params.Validate(); err != nil {
return err
plans := k.GetAllPlans(ctx)
stakings := k.GetAllStakings(ctx)
rewards := k.GetAllRewards(ctx)

for _, plan := range plans {
any, err := types.PackPlan(plan)
if err != nil {
panic(err)
}
planRecords = append(planRecords, types.PlanRecord{
Plan: *any,
FarmingPoolCoins: k.bankKeeper.GetAllBalances(ctx, plan.GetFarmingPoolAddress()),
})
}

cc, _ := ctx.CacheContext()
k.SetParams(cc, genState.Params)

// TODO: unimplemented
//for _, record := range genState.PlanRecords {
// record = k.SetPlanRecord(cc, record)
// if err := k.ValidatePlanRecord(cc, record); err != nil {
// return err
// }
//}

return nil
epochTime, _ := k.GetLastEpochTime(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LastEpochTime may be not present on the store so the second return value found can be false. In that case, calling NewGenesisState with concrete time.Time value will not act as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. If there's no value(false), the expectation is to return time.Time{} and set it to this empty time.Time{} value for export genesis. Can't we do this?

return types.NewGenesisState(params, planRecords, stakings, rewards, k.bankKeeper.GetAllBalances(ctx, types.StakingReserveAcc), k.bankKeeper.GetAllBalances(ctx, types.RewardsReserveAcc), epochTime)
}
68 changes: 68 additions & 0 deletions x/farming/keeper/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package keeper_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/tendermint/farming/x/farming/types"
)

func (suite *KeeperTestSuite) TestInitGenesis() {
plans := []types.PlanI{
types.NewFixedAmountPlan(
types.NewBasePlan(
1,
"",
types.PlanTypePrivate,
suite.addrs[0].String(),
suite.addrs[0].String(),
sdk.NewDecCoins(
sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)),
sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1))),
mustParseRFC3339("2021-07-30T00:00:00Z"),
mustParseRFC3339("2021-08-30T00:00:00Z"),
),
sdk.NewCoins(sdk.NewInt64Coin(denom3, 1_000_000))),
types.NewRatioPlan(
types.NewBasePlan(
2,
"",
types.PlanTypePublic,
suite.addrs[0].String(),
suite.addrs[0].String(),
sdk.NewDecCoins(
sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)),
sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1))),
mustParseRFC3339("2021-07-30T00:00:00Z"),
mustParseRFC3339("2021-08-30T00:00:00Z"),
),
sdk.MustNewDecFromStr("0.01")),
}
//for _, plan := range plans {
// suite.keeper.SetPlan(suite.ctx, plan)
//}
suite.keeper.SetPlan(suite.ctx, plans[1])
suite.keeper.SetPlan(suite.ctx, plans[0])

suite.Stake(suite.addrs[1], sdk.NewCoins(
sdk.NewInt64Coin(denom1, 1_000_000),
sdk.NewInt64Coin(denom2, 1_000_000)))
suite.keeper.ProcessQueuedCoins(suite.ctx)

suite.ctx = suite.ctx.WithBlockTime(mustParseRFC3339("2021-07-31T00:00:00Z"))
err := suite.keeper.DistributeRewards(suite.ctx)
suite.Require().NoError(err)

var genState *types.GenesisState
suite.Require().NotPanics(func() {
genState = suite.keeper.ExportGenesis(suite.ctx)
},
)
err = types.ValidateGenesis(*genState)
suite.Require().NoError(err)

suite.Require().NotPanics(func() {
suite.keeper.InitGenesis(suite.ctx, *genState)
},
)
suite.Require().Equal(genState, suite.keeper.ExportGenesis(suite.ctx))
}
27 changes: 4 additions & 23 deletions x/farming/keeper/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,8 @@ func AllInvariants(k Keeper) sdk.Invariant {
// StakingReservedAmountInvariant checks that the balance of StakingReserveAcc greater than the amount of staked, Queued coins in all staking objects.
func StakingReservedAmountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var totalStakingAmt sdk.Coins
balanceStakingReserveAcc := k.bankKeeper.GetAllBalances(ctx, types.StakingReserveAcc)

k.IterateAllStakings(ctx, func(staking types.Staking) (stop bool) {
totalStakingAmt = totalStakingAmt.Add(staking.StakedCoins...).Add(staking.QueuedCoins...)
return false
})

broken := !balanceStakingReserveAcc.IsAllGTE(totalStakingAmt)
err := k.ValidateStakingReservedAmount(ctx)
broken := err != nil
return sdk.FormatInvariant(types.ModuleName, "staking reserved amount invariant broken",
"the balance of StakingReserveAcc less than the amount of staked, Queued coins in all staking objects"), broken
}
Expand All @@ -45,20 +38,8 @@ func StakingReservedAmountInvariant(k Keeper) sdk.Invariant {
// RemainingRewardsAmountInvariant checks that the balance of the RewardPoolAddresses of all plans greater than the total amount of unwithdrawn reward coins in all reward objects
func RemainingRewardsAmountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var totalRemainingRewards sdk.Coins
//var totalBalancesRewardPools sdk.Coins
//k.IterateAllPlans(ctx, func(plan types.PlanI) (stop bool) {
// totalBalancesRewardPools = totalBalancesRewardPools.Add(k.bankKeeper.GetAllBalances(ctx, plan.GetRewardPoolAddress())...)
// return false
//})
totalBalancesRewardPool := k.bankKeeper.GetAllBalances(ctx, k.GetRewardsReservePoolAcc(ctx))
k.IterateAllRewards(ctx, func(reward types.Reward) (stop bool) {
totalRemainingRewards = totalRemainingRewards.Add(reward.RewardCoins...)
return false
})

//broken := !totalBalancesRewardPools.IsAllGTE(totalRemainingRewards)
broken := !totalBalancesRewardPool.IsAllGTE(totalRemainingRewards)
err := k.ValidateRemainingRewardsAmount(ctx)
broken := err != nil
return sdk.FormatInvariant(types.ModuleName, "remaining rewards amount invariant broken",
"the balance of the RewardPoolAddresses of all plans less than the total amount of unwithdrawn reward coins in all reward objects"), broken
}
Expand Down
40 changes: 38 additions & 2 deletions x/farming/keeper/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ func (k Keeper) SetPlanIdByFarmerAddrIndex(ctx sdk.Context, farmerAcc sdk.AccAdd
// GetNextPlanIdWithUpdate returns and increments the global Plan ID counter.
// If the global plan number is not set, it initializes it with value 0.
func (k Keeper) GetNextPlanIdWithUpdate(ctx sdk.Context) uint64 {
store := ctx.KVStore(k.storeKey)
id := k.GetGlobalPlanId(ctx) + 1
k.SetGlobalPlanId(ctx, id)
return id
}

// SetGlobalPlanId set the global Plan ID counter.
func (k Keeper) SetGlobalPlanId(ctx sdk.Context, id uint64) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&gogotypes.UInt64Value{Value: id})
store.Set(types.GlobalPlanIdKey, bz)
return id
}

// GetGlobalPlanId returns the global Plan ID counter.
Expand Down Expand Up @@ -307,3 +312,34 @@ func (k Keeper) GeneratePrivatePlanFarmingPoolAddress(ctx sdk.Context, name stri
}
return poolAcc, nil
}

// ValidateStakingReservedAmount checks that the balance of StakingReserveAcc greater than the amount of staked, Queued coins in all staking objects.
func (k Keeper) ValidateStakingReservedAmount(ctx sdk.Context) error {
var totalStakingAmt sdk.Coins
balanceStakingReserveAcc := k.bankKeeper.GetAllBalances(ctx, types.StakingReserveAcc)

k.IterateAllStakings(ctx, func(staking types.Staking) (stop bool) {
totalStakingAmt = totalStakingAmt.Add(staking.StakedCoins...).Add(staking.QueuedCoins...)
return false
})

if !balanceStakingReserveAcc.IsAllGTE(totalStakingAmt) {
return types.ErrInvalidStakingReservedAmount
}
return nil
}

// ValidateRemainingRewardsAmount checks that the balance of the RewardPoolAddresses of all plans greater than the total amount of unwithdrawn reward coins in all reward objects
func (k Keeper) ValidateRemainingRewardsAmount(ctx sdk.Context) error {
var totalRemainingRewards sdk.Coins
totalBalancesRewardPool := k.bankKeeper.GetAllBalances(ctx, k.GetRewardsReservePoolAcc(ctx))
k.IterateAllRewards(ctx, func(reward types.Reward) (stop bool) {
totalRemainingRewards = totalRemainingRewards.Add(reward.RewardCoins...)
return false
})

if !totalBalancesRewardPool.IsAllGTE(totalRemainingRewards) {
return types.ErrInvalidRemainingRewardsAmount
}
return nil
}
5 changes: 5 additions & 0 deletions x/farming/spec/02_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type PlanI interface {

GetId() uint64
SetId(uint64) error

GetName() string
SetName(name string) error

GetType() int32
SetType(int32) error
Expand Down Expand Up @@ -44,6 +47,8 @@ type PlanI interface {
SetDistributedCoins(sdk.Coins) error

String() string

Validate() error
}
```

Expand Down
2 changes: 2 additions & 0 deletions x/farming/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ var (
ErrDuplicatePlanName = sdkerrors.Register(ModuleName, 9, "duplicate plan name")
ErrInvalidPlanName = sdkerrors.Register(ModuleName, 10, "invalid plan name")
ErrConflictPrivatePlanFarmingPool = sdkerrors.Register(ModuleName, 11, "the address is already in use, please use a different plan name")
ErrInvalidStakingReservedAmount = sdkerrors.Register(ModuleName, 12, "staking reserved amount invariant broken")
ErrInvalidRemainingRewardsAmount = sdkerrors.Register(ModuleName, 13, "remaining rewards amount invariant broken")
)
Loading