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

fix: validation totalRate to check date overlapped budgets #83

Merged
21 changes: 5 additions & 16 deletions x/budget/keeper/budget.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,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 @@ -67,21 +71,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