Skip to content

Commit

Permalink
Merge pull request #83 from dongsam/82-fix-collectible-budget-and-val…
Browse files Browse the repository at this point in the history
…idation

fix: validation totalRate to check date overlapped budgets
  • Loading branch information
dongsam authored Nov 8, 2021
2 parents c20c700 + 06dc75e commit e20df59
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 111 deletions.
21 changes: 5 additions & 16 deletions x/budget/keeper/budget.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import (
// CollectBudgets collects all the valid budgets registered in params.Budgets and
// distributes the total collected coins to collection address.
func (k Keeper) CollectBudgets(ctx sdk.Context) error {
budgets := k.CollectibleBudgets(ctx)
params := k.GetParams(ctx)
var budgets []types.Budget
if params.EpochBlocks > 0 && ctx.BlockHeight()%int64(params.EpochBlocks) == 0 {
budgets = types.CollectibleBudgets(params.Budgets, ctx.BlockTime())
}
if len(budgets) == 0 {
return nil
}
Expand Down Expand Up @@ -90,21 +94,6 @@ func (k Keeper) CollectBudgets(ctx sdk.Context) error {
return nil
}

// CollectibleBudgets returns scan through the budgets registered in params.Budgets
// and returns only the valid and not expired budgets.
func (k Keeper) CollectibleBudgets(ctx sdk.Context) (budgets []types.Budget) {
params := k.GetParams(ctx)
if params.EpochBlocks > 0 && ctx.BlockHeight()%int64(params.EpochBlocks) == 0 {
for _, budget := range params.Budgets {
err := budget.Validate()
if err == nil && budget.Collectible(ctx.BlockTime()) {
budgets = append(budgets, budget)
}
}
}
return
}

// GetTotalCollectedCoins returns total collected coins for a budget.
func (k Keeper) GetTotalCollectedCoins(ctx sdk.Context, budgetName string) sdk.Coins {
store := ctx.KVStore(k.storeKey)
Expand Down
61 changes: 45 additions & 16 deletions x/budget/keeper/budget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
params := suite.keeper.GetParams(suite.ctx)
suite.keeper.SetParams(suite.ctx, params)
height := 1
suite.ctx = suite.ctx.WithBlockTime(mustParseRFC3339("2021-08-01T00:00:00Z"))
suite.ctx = suite.ctx.WithBlockTime(types.MustParseRFC3339("2021-08-01T00:00:00Z"))
suite.ctx = suite.ctx.WithBlockHeight(int64(height))

for _, tc := range []struct {
Expand All @@ -196,7 +196,6 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
nextBlockTime time.Time
expErr error
}{

{
"add budget 1",
testProposal(proposal.ParamChange{
Expand All @@ -215,8 +214,8 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
}),
1,
0,
mustParseRFC3339("2021-08-01T00:00:00Z"),
mustParseRFC3339("2021-08-01T00:00:00Z"),
types.MustParseRFC3339("2021-08-01T00:00:00Z"),
types.MustParseRFC3339("2021-08-01T00:00:00Z"),
nil,
},
{
Expand Down Expand Up @@ -245,8 +244,8 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
}),
2,
2,
mustParseRFC3339("2021-09-03T00:00:00Z"),
mustParseRFC3339("2021-09-03T00:00:00Z"),
types.MustParseRFC3339("2021-09-03T00:00:00Z"),
types.MustParseRFC3339("2021-09-03T00:00:00Z"),
nil,
},
{
Expand Down Expand Up @@ -283,8 +282,8 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
}),
0,
0,
mustParseRFC3339("2021-09-29T00:00:00Z"),
mustParseRFC3339("2021-09-30T00:00:00Z"),
types.MustParseRFC3339("2021-09-29T00:00:00Z"),
types.MustParseRFC3339("2021-09-30T00:00:00Z"),
types.ErrInvalidTotalBudgetRate,
},
{
Expand Down Expand Up @@ -321,8 +320,8 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
}),
0,
0,
mustParseRFC3339("2021-10-01T00:00:00Z"),
mustParseRFC3339("2021-10-01T00:00:00Z"),
types.MustParseRFC3339("2021-10-01T00:00:00Z"),
types.MustParseRFC3339("2021-10-01T00:00:00Z"),
types.ErrInvalidTotalBudgetRate,
},
{
Expand Down Expand Up @@ -351,8 +350,38 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
}),
2,
2,
mustParseRFC3339("2021-10-01T00:00:00Z"),
mustParseRFC3339("2021-10-01T00:00:00Z"),
types.MustParseRFC3339("2021-10-01T00:00:00Z"),
types.MustParseRFC3339("2021-10-01T00:00:00Z"),
nil,
},
{
"add budget 4 without date range overlap",
testProposal(proposal.ParamChange{
Subspace: types.ModuleName,
Key: string(types.KeyBudgets),
Value: `[
{
"name": "gravity-dex-farming-20213Q-20313Q",
"rate": "0.500000000000000000",
"budget_source_address": "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta",
"collection_address": "cosmos1228ryjucdpdv3t87rxle0ew76a56ulvnfst0hq0sscd3nafgjpqqkcxcky",
"start_time": "2021-09-01T00:00:00Z",
"end_time": "2031-09-30T00:00:00Z"
},
{
"name": "gravity-dex-farming-4",
"rate": "1.000000000000000000",
"budget_source_address": "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta",
"collection_address": "cosmos17avp6xs5c8ycqzy20yv99ccxwunu32e507kpm8ql5nfg47pzj9qqxhujxr",
"start_time": "2031-09-30T00:00:01Z",
"end_time": "2031-12-10T00:00:00Z"
}
]`,
}),
2,
1,
types.MustParseRFC3339("2021-09-29T00:00:00Z"),
types.MustParseRFC3339("2021-09-30T00:00:00Z"),
nil,
},
} {
Expand Down Expand Up @@ -384,7 +413,7 @@ func (suite *KeeperTestSuite) TestBudgetChangeSituation() {
height += 1
suite.ctx = suite.ctx.WithBlockHeight(int64(height))
suite.ctx = suite.ctx.WithBlockTime(tc.nextBlockTime)
budgets := suite.keeper.CollectibleBudgets(suite.ctx)
budgets := types.CollectibleBudgets(params.Budgets, suite.ctx.BlockTime())
suite.Require().Len(budgets, tc.collectibleBudgetCount)

// BeginBlocker
Expand Down Expand Up @@ -418,8 +447,8 @@ func (suite *KeeperTestSuite) TestTotalCollectedCoins() {
Rate: sdk.NewDecWithPrec(5, 2), // 5%
BudgetSourceAddress: suite.budgetSourceAddrs[0].String(),
CollectionAddress: suite.collectionAddrs[0].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
}

params := suite.keeper.GetParams(suite.ctx)
Expand All @@ -432,7 +461,7 @@ func (suite *KeeperTestSuite) TestTotalCollectedCoins() {
collectedCoins := suite.keeper.GetTotalCollectedCoins(suite.ctx, "budget1")
suite.Require().Equal(sdk.Coins(nil), collectedCoins)

suite.ctx = suite.ctx.WithBlockTime(mustParseRFC3339("2021-08-31T00:00:00Z"))
suite.ctx = suite.ctx.WithBlockTime(types.MustParseRFC3339("2021-08-31T00:00:00Z"))
err := suite.keeper.CollectBudgets(suite.ctx)
suite.Require().NoError(err)

Expand Down
18 changes: 9 additions & 9 deletions x/budget/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ func (suite *KeeperTestSuite) TestGRPCBudgets() {
Rate: sdk.NewDecWithPrec(5, 2),
BudgetSourceAddress: suite.budgetSourceAddrs[0].String(),
CollectionAddress: suite.collectionAddrs[0].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget2",
Rate: sdk.NewDecWithPrec(5, 2),
BudgetSourceAddress: suite.budgetSourceAddrs[0].String(),
CollectionAddress: suite.collectionAddrs[1].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget3",
Rate: sdk.NewDecWithPrec(5, 2),
BudgetSourceAddress: suite.budgetSourceAddrs[1].String(),
CollectionAddress: suite.collectionAddrs[0].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget4",
Rate: sdk.NewDecWithPrec(5, 2),
BudgetSourceAddress: suite.budgetSourceAddrs[1].String(),
CollectionAddress: suite.collectionAddrs[1].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
}

Expand All @@ -57,7 +57,7 @@ func (suite *KeeperTestSuite) TestGRPCBudgets() {
balance := suite.app.BankKeeper.GetAllBalances(suite.ctx, suite.budgetSourceAddrs[0])
expectedCoins, _ := sdk.NewDecCoinsFromCoins(balance...).MulDec(sdk.NewDecWithPrec(5, 2)).TruncateDecimal()

suite.ctx = suite.ctx.WithBlockTime(mustParseRFC3339("2021-08-31T00:00:00Z"))
suite.ctx = suite.ctx.WithBlockTime(types.MustParseRFC3339("2021-08-31T00:00:00Z"))
err := suite.keeper.CollectBudgets(suite.ctx)
suite.Require().NoError(err)

Expand Down
37 changes: 14 additions & 23 deletions x/budget/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package keeper_test

import (
"testing"
"time"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
Expand Down Expand Up @@ -91,56 +90,56 @@ func (suite *KeeperTestSuite) SetupTest() {
Rate: sdk.MustNewDecFromStr("0.5"),
BudgetSourceAddress: suite.budgetSourceAddrs[0].String(),
CollectionAddress: suite.collectionAddrs[0].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget2",
Rate: sdk.MustNewDecFromStr("0.5"),
BudgetSourceAddress: suite.budgetSourceAddrs[0].String(),
CollectionAddress: suite.collectionAddrs[1].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget3",
Rate: sdk.MustNewDecFromStr("1.0"),
BudgetSourceAddress: suite.budgetSourceAddrs[1].String(),
CollectionAddress: suite.collectionAddrs[2].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget4",
Rate: sdk.MustNewDecFromStr("1"),
BudgetSourceAddress: suite.budgetSourceAddrs[2].String(),
CollectionAddress: suite.collectionAddrs[3].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("0000-01-02T00:00:00Z"),
},
{
Name: "budget5",
Rate: sdk.MustNewDecFromStr("0.5"),
BudgetSourceAddress: suite.budgetSourceAddrs[3].String(),
CollectionAddress: suite.collectionAddrs[0].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "budget6",
Rate: sdk.MustNewDecFromStr("0.5"),
BudgetSourceAddress: suite.budgetSourceAddrs[3].String(),
CollectionAddress: suite.collectionAddrs[1].String(),
StartTime: mustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: mustParseRFC3339("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("0000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
},
{
Name: "gravity-dex-farming-20213Q-20313Q",
Rate: sdk.MustNewDecFromStr("0.5"),
BudgetSourceAddress: suite.budgetSourceAddrs[5].String(),
CollectionAddress: suite.collectionAddrs[5].String(),
StartTime: mustParseRFC3339("2021-09-01T00:00:00Z"),
EndTime: mustParseRFC3339("2031-09-30T00:00:00Z"),
StartTime: types.MustParseRFC3339("2021-09-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("2031-09-30T00:00:00Z"),
},
}
}
Expand All @@ -149,14 +148,6 @@ func coinsEq(exp, got sdk.Coins) (bool, string, string, string) {
return exp.IsEqual(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
}

func mustParseRFC3339(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(err)
}
return t
}

func mustParseCoinsNormalized(coinStr string) (coins sdk.Coins) {
coins, err := sdk.ParseCoinsNormalized(coinStr)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions x/budget/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func GenBudgets(r *rand.Rand) []types.Budget {
Rate: sdk.NewDecFromIntWithPrec(sdk.NewInt(int64(simtypes.RandIntBetween(r, 1, 4))), 1), // 10~30%
BudgetSourceAddress: "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta", // Cosmos Hub's FeeCollector module account
CollectionAddress: sdk.AccAddress(address.Module(types.ModuleName, []byte("GravityDEXFarmingBudget"))).String(),
StartTime: types.ParseTime("2000-01-01T00:00:00Z"),
EndTime: types.ParseTime("9999-12-31T00:00:00Z"),
StartTime: types.MustParseRFC3339("2000-01-01T00:00:00Z"),
EndTime: types.MustParseRFC3339("9999-12-31T00:00:00Z"),
}
ranBudgets = append(ranBudgets, budget)
}
Expand Down
12 changes: 11 additions & 1 deletion x/budget/types/budget.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (budget Budget) Validate() error {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid budget source address %s: %v", budget.BudgetSourceAddress, err)
}

if budget.EndTime.Before(budget.StartTime) {
if !budget.EndTime.After(budget.StartTime) {
return ErrInvalidStartEndTime
}

Expand All @@ -63,6 +63,16 @@ func (budget Budget) Collectible(blockTime time.Time) bool {
return !budget.StartTime.After(blockTime) && budget.EndTime.After(blockTime)
}

// CollectibleBudgets returns only the valid and started and not expired budgets based on the given block time.
func CollectibleBudgets(budgets []Budget, blockTime time.Time) (collectibleBudgets []Budget) {
for _, budget := range budgets {
if budget.Collectible(blockTime) {
collectibleBudgets = append(collectibleBudgets, budget)
}
}
return
}

// ValidateName is the default validation function for Budget.Name.
// A budget name only allows alphabet letters(`A-Z, a-z`), digit numbers(`0-9`), and `-`.
// It doesn't allow spaces and the maximum length is 50 characters.
Expand Down
2 changes: 1 addition & 1 deletion x/budget/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// Sentinel errors for the budget module.
var (
ErrInvalidBudgetName = sdkerrors.Register(ModuleName, 2, "budget name only allows letters, digits, and dash(-) without spaces and the maximum length is 50")
ErrInvalidStartEndTime = sdkerrors.Register(ModuleName, 3, "budget end time should not be earlier than budget start time")
ErrInvalidStartEndTime = sdkerrors.Register(ModuleName, 3, "budget end time must be after the start time")
ErrInvalidBudgetRate = sdkerrors.Register(ModuleName, 4, "invalid budget rate")
ErrInvalidTotalBudgetRate = sdkerrors.Register(ModuleName, 5, "invalid total rate of the budgets with the same budget source address")
ErrDuplicateBudgetName = sdkerrors.Register(ModuleName, 6, "duplicate budget name")
Expand Down
24 changes: 18 additions & 6 deletions x/budget/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,25 @@ func ValidateBudgets(i interface{}) error {
}
names[budget.Name] = true
}

budgetsBySourceMap := GetBudgetsBySourceMap(budgets)
for addr, budgets := range budgetsBySourceMap {
if budgets.TotalRate.GT(sdk.OneDec()) {
return sdkerrors.Wrapf(
ErrInvalidTotalBudgetRate,
"total rate for budget source address %s must not exceed 1: %v", addr, budgets.TotalRate)
for addr, budgetsBySource := range budgetsBySourceMap {
if budgetsBySource.TotalRate.GT(sdk.OneDec()) {
// If the TotalRate of Budgets with the same BudgetSourceAddress exceeds 1,
// recalculate and verify the TotalRate of Budgets with overlapping time ranges.
for _, budget := range budgetsBySource.Budgets {
totalRate := sdk.ZeroDec()
for _, budgetToCheck := range budgetsBySource.Budgets {
if DateRangesOverlap(budget.StartTime, budget.EndTime, budgetToCheck.StartTime, budgetToCheck.EndTime) {
totalRate = totalRate.Add(budgetToCheck.Rate)
}
}
if totalRate.GT(sdk.OneDec()) {
return sdkerrors.Wrapf(
ErrInvalidTotalBudgetRate,
"total rate for budget source address %s must not exceed 1: %v", addr, totalRate)
}
}

}
}
return nil
Expand Down
Loading

0 comments on commit e20df59

Please sign in to comment.