Skip to content

Commit

Permalink
feat: fix epoch params (#103)
Browse files Browse the repository at this point in the history
* feat: add global keys prefix for the current epoch days

* test: add test for key store prefixes

* chore: fix broken store prefix test, rename EpochDays to NextEpochDays

* test: add more tests and update address to have 20 bytes

* test: remove comments

* test: update epoch days to next epoch days

* test: add handler tests

* refactor: add comment for global current epoch days

* test: apply module testing suit

* test: remove tests for deprecated PlansByFarmerIndex

* feat: move mustParseRFC3339 function to utils #109

* docs: update spec docs

* feat: adding test for end blocker

* chore: rename GlobalCurrentEpochDays to CurrentEpochDays and refactor codes

* test: improve code coverage

* chore: apply code review feedbacks and suggestions

* feat: add gRPC query and cli for current epoch days

* fix: apply code review feedbacks and suggestions

* fix: resolve conflicts

* fix: panic when unstake due to total stakings is not set
  • Loading branch information
jaybxyz authored Sep 16, 2021
1 parent 1e6f946 commit f03a6b8
Show file tree
Hide file tree
Showing 44 changed files with 1,747 additions and 1,791 deletions.
1,545 changes: 234 additions & 1,311 deletions client/docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions proto/tendermint/farming/v1beta1/farming.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ message Params {
(gogoproto.nullable) = false
];

// The universal epoch length in number of days. Every process for staking and
// reward distribution is executed with this epoch_days frequency
uint32 epoch_days = 2 [(gogoproto.moretags) = "yaml:\"epoch_days\""];
// next_epoch_days is the epoch length in number of days
// it updates internal state called CurrentEpochDays that is used to process
// staking and reward distribution in end blocker
uint32 next_epoch_days = 2 [(gogoproto.moretags) = "yaml:\"next_epoch_days\""];

// farming_fee_collector is the module account address to collect fees within the farming module
string farming_fee_collector = 3 [(gogoproto.moretags) = "yaml:\"farming_fee_collector\""];
Expand Down
3 changes: 3 additions & 0 deletions proto/tendermint/farming/v1beta1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ message GenesisState {
// last_epoch_time specifies the last executed epoch time of the plans
google.protobuf.Timestamp last_epoch_time = 10
[(gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"last_epoch_time\""];

// current_epoch_days specifies the epoch used when allocating farming rewards in end blocker
uint32 current_epoch_days = 11;
}

// PlanRecord is used for import/export via genesis json.
Expand Down
13 changes: 13 additions & 0 deletions proto/tendermint/farming/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ service Query {
rpc Rewards(QueryRewardsRequest) returns (QueryRewardsResponse) {
option (google.api.http).get = "/cosmos/farming/v1beta1/rewards/{farmer}";
}

// CurrentEpochDays returns current epoch days.
rpc CurrentEpochDays(QueryCurrentEpochDaysRequest) returns (QueryCurrentEpochDaysResponse) {
option (google.api.http).get = "/cosmos/farming/v1beta1/current_epoch_days";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down Expand Up @@ -105,3 +110,11 @@ message QueryRewardsResponse {
repeated cosmos.base.v1beta1.Coin rewards = 1
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
}

// QueryCurrentEpochDaysRequest is the request type for the Query/CurrentEpochDays RPC method.
message QueryCurrentEpochDaysRequest {}

// QuerCurrentEpochDaysResponse is the response type for the Query/CurrentEpochDays RPC method.
message QueryCurrentEpochDaysResponse {
uint32 current_epoch_days = 1;
}
11 changes: 9 additions & 2 deletions x/farming/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,25 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
}
}

params := k.GetParams(ctx)
// CurrentEpochDays is intialized with the value of NextEpochDays in genesis and
// it is used here to prevent from affecting the epoch days for farming rewards allocation.
// Suppose NextEpochDays is 7 days and it is proposed to change the value to 1 day through governance proposal.
// Although the proposal is passed, farming rewards allocation should continue to proceed with 7 days and then it gets updated.
currentEpochDays := k.GetCurrentEpochDays(ctx)

lastEpochTime, found := k.GetLastEpochTime(ctx)
if !found {
k.SetLastEpochTime(ctx, ctx.BlockTime())
} else {
y, m, d := lastEpochTime.AddDate(0, 0, int(params.EpochDays)).Date()
y, m, d := lastEpochTime.AddDate(0, 0, int(currentEpochDays)).Date()
y2, m2, d2 := ctx.BlockTime().Date()
if !time.Date(y2, m2, d2, 0, 0, 0, 0, time.UTC).Before(time.Date(y, m, d, 0, 0, 0, 0, time.UTC)) {
if err := k.AdvanceEpoch(ctx); err != nil {
panic(err)
}
if params := k.GetParams(ctx); params.NextEpochDays != currentEpochDays {
k.SetCurrentEpochDays(ctx, params.NextEpochDays)
}
}
}
}
64 changes: 64 additions & 0 deletions x/farming/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package farming_test

import (
"time"

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

_ "github.com/stretchr/testify/suite"
)

func (suite *ModuleTestSuite) TestEndBlockerEpochDaysTest() {
epochDaysTest := func(formerEpochDays, targetNextEpochDays uint32) {
suite.SetupTest()

params := suite.keeper.GetParams(suite.ctx)
params.NextEpochDays = formerEpochDays
suite.keeper.SetParams(suite.ctx, params)
suite.keeper.SetCurrentEpochDays(suite.ctx, formerEpochDays)

t := types.ParseTime("2021-08-01T00:00:00Z")
suite.ctx = suite.ctx.WithBlockTime(t)
farming.EndBlocker(suite.ctx, suite.keeper)

lastEpochTime, _ := suite.keeper.GetLastEpochTime(suite.ctx)

for i := 1; i < 200; i++ {
t = t.Add(1 * time.Hour)
suite.ctx = suite.ctx.WithBlockTime(t)
farming.EndBlocker(suite.ctx, suite.keeper)

if i == 10 { // 10 hours passed
params := suite.keeper.GetParams(suite.ctx)
params.NextEpochDays = targetNextEpochDays
suite.keeper.SetParams(suite.ctx, params)
}

currentEpochDays := suite.keeper.GetCurrentEpochDays(suite.ctx)
t2, _ := suite.keeper.GetLastEpochTime(suite.ctx)

if uint32(i) == formerEpochDays*24 {
suite.Require().True(t2.After(lastEpochTime))
suite.Require().Equal(t2.Sub(lastEpochTime).Hours(), float64(formerEpochDays*24))
suite.Require().Equal(targetNextEpochDays, currentEpochDays)
}

if uint32(i) == formerEpochDays*24+targetNextEpochDays*24 {
suite.Require().Equal(t2.Sub(lastEpochTime).Hours(), float64(currentEpochDays*24))
suite.Require().Equal(targetNextEpochDays, currentEpochDays)
}

lastEpochTime = t2
}
}

// increasing case
epochDaysTest(1, 7)

// decreasing case
epochDaysTest(7, 1)

// stay case
epochDaysTest(1, 1)
}
62 changes: 27 additions & 35 deletions x/farming/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package cli_test
import (
"fmt"
"testing"
"time"

"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
Expand All @@ -19,6 +18,7 @@ import (

"github.com/tendermint/farming/x/farming/client/cli"
farmingtestutil "github.com/tendermint/farming/x/farming/client/testutil"
"github.com/tendermint/farming/x/farming/types"
farmingtypes "github.com/tendermint/farming/x/farming/types"
)

Expand Down Expand Up @@ -82,8 +82,8 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() {
case1 := cli.PrivateFixedPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)),
}

Expand All @@ -94,44 +94,44 @@ func (s *IntegrationTestSuite) TestNewCreateFixedAmountPlanCmd() {
OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM
OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM`,
StakingCoinWeights: sdk.NewDecCoins(),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)),
}

// invalid staking coin weights
case3 := cli.PrivateFixedPlanRequest{
Name: name,
StakingCoinWeights: sdk.NewDecCoins(),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)),
}

// invalid staking coin weights
case4 := cli.PrivateFixedPlanRequest{
Name: name,
StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("poolD35A0CC16EE598F90B044CE296A405BA9C381E38837599D96F2F70C2F02A23A4", sdk.NewInt(2))),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)),
}

// invalid start time and end time
case5 := cli.PrivateFixedPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"),
EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"),
StartTime: types.ParseTime("2021-08-13T00:00:00Z"),
EndTime: types.ParseTime("2021-08-06T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 100_000_000)),
}

// invalid epoch amount
case6 := cli.PrivateFixedPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("uatom", 0)),
}

Expand Down Expand Up @@ -247,8 +247,8 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() {
case1 := cli.PrivateRatioPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("0.1"),
}

Expand All @@ -259,44 +259,44 @@ func (s *IntegrationTestSuite) TestNewCreateRatioPlanCmd() {
OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM
OVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERMOVERMAXLENGTHOVERMAXLENGTHOVERMAXLENGTHOVERM`,
StakingCoinWeights: sdk.NewDecCoins(),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("0.1"),
}

// invalid staking coin weights
case3 := cli.PrivateRatioPlanRequest{
Name: name,
StakingCoinWeights: sdk.NewDecCoins(),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("0.1"),
}

// invalid staking coin weights
case4 := cli.PrivateRatioPlanRequest{
Name: name,
StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("poolD35A0CC16EE598F90B044CE296A405BA9C381E38837599D96F2F70C2F02A23A4", sdk.NewInt(2))),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("0.1"),
}

// invalid start time and end time
case5 := cli.PrivateRatioPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("2021-08-13T00:00:00Z"),
EndTime: mustParseRFC3339("2021-08-06T00:00:00Z"),
StartTime: types.ParseTime("2021-08-13T00:00:00Z"),
EndTime: types.ParseTime("2021-08-06T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("0.1"),
}

// invalid epoch ratio
case6 := cli.PrivateRatioPlanRequest{
Name: name,
StakingCoinWeights: coinWeights,
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochRatio: sdk.MustNewDecFromStr("1.1"),
}

Expand Down Expand Up @@ -525,8 +525,8 @@ func (s *IntegrationTestSuite) TestNewHarvestCmd() {
req := cli.PrivateFixedPlanRequest{
Name: "test",
StakingCoinWeights: sdk.NewDecCoins(sdk.NewDecCoin("stake", sdk.NewInt(1))),
StartTime: mustParseRFC3339("0001-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-01-01T00:00:00Z"),
StartTime: types.ParseTime("0001-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-01-01T00:00:00Z"),
EpochAmount: sdk.NewCoins(sdk.NewInt64Coin("node0token", 100_000_000)),
}

Expand Down Expand Up @@ -601,11 +601,3 @@ func (s *IntegrationTestSuite) TestNewHarvestCmd() {
})
}
}

func mustParseRFC3339(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(err)
}
return t
}
40 changes: 37 additions & 3 deletions x/farming/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func GetQueryCmd() *cobra.Command {
GetCmdQueryStakings(),
GetCmdQueryTotalStakings(),
GetCmdQueryRewards(),
GetCmdQueryCurrentEpochDays(),
)

return farmingQueryCmd
Expand All @@ -49,7 +50,6 @@ func GetCmdQueryParams() *cobra.Command {
Short: "Query the current farming parameters information",
Long: strings.TrimSpace(
fmt.Sprintf(`Query values set as farming parameters.
Example:
$ %s query %s params
`,
Expand Down Expand Up @@ -85,7 +85,6 @@ func GetCmdQueryPlans() *cobra.Command {
Short: "Query for all plans",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details about all farming plans on a network.
Example:
$ %s query %s plans
$ %s query %s plans --plan-type private
Expand Down Expand Up @@ -156,7 +155,6 @@ func GetCmdQueryPlan() *cobra.Command {
Short: "Query a specific plan",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details about a specific plan.
Example:
$ %s query %s plan
`,
Expand Down Expand Up @@ -337,3 +335,39 @@ $ %s query %s rewards %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj --staking-coin-d

return cmd
}

func GetCmdQueryCurrentEpochDays() *cobra.Command {
cmd := &cobra.Command{
Use: "current-epoch-days",
Args: cobra.NoArgs,
Short: "Query the value of current epoch days",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the value set as current epoch days.
Example:
$ %s query %s current-epoch-days
`,
version.AppName, types.ModuleName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

resp, err := queryClient.CurrentEpochDays(context.Background(), &types.QueryCurrentEpochDaysRequest{})
if err != nil {
return err
}

return clientCtx.PrintProto(resp)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
Loading

0 comments on commit f03a6b8

Please sign in to comment.